码迷,mamicode.com
首页 > 移动开发 > 详细

Android开发笔记(一百)折叠式列表

时间:2016-05-13 15:06:02      阅读:266      评论:0      收藏:0      [点我收藏+]

标签:

更多动态视图MoreNewsView

经常看朋友圈的动态,有的动态内容较多就只展示前面一段,如果用户想看完整的再点击展开,这样整个页面的动态列表比较均衡,不会出现个别动态占用大片屏幕的情况。同样,查看博客的文章列表也类似,只展示文章开头几行内容,有需要再点击加载全篇文章。


动态列表直接使用ListView,动态内容就得自己写个控件了,自定义控件的难点在于如何把握动态下拉和收起的动画。这里我们要先预习TextView的相关函数,下面是本文用到的方法说明:
getHeight : 获取TextView的显示高度。
setHeight : 设置TextView的显示高度。
getLineHeight : 获取每行文本的高度。
getLineCount : 获取所有文本的行数。


如果一开始每条动态默认显示四行,那么默认显示高度是getLineHeight*4,使用setHeight方法即可设置动态的初始显示高度。点击展开动态全文时,就得显示所有行的文本,整个文本的高度是getLineHeight*getLineCount。现在有了每条动态的初始高度,以及动态全文的完整高度,再加个拉伸动画就差不多了。拉伸动画的主要工作是随着时间的推移,给TextView设置渐增或渐减的高度,这要重写Animation的applyTransformation方法。


下面是点击监听器的显示动画代码示例:
	private OnClickListener mOnClickListener = new View.OnClickListener() {
		boolean isExpand;
		@Override
		public void onClick(View v) {
			tv_expand.setText(isExpand?"查看全文":"收起关注");
			isExpand = !isExpand;
			tv_content.clearAnimation();
			final int deltaValue;
			final int startValue = tv_content.getHeight();
			int durationMillis = 300;
			if (isExpand) {
				deltaValue = tv_content.getLineHeight() * tv_content.getLineCount() - startValue;
			} else {
				deltaValue = tv_content.getLineHeight() * maxLine - startValue;
			}
			Animation animation = new Animation() {
				protected void applyTransformation(float interpolatedTime, Transformation t) {
					tv_content.setHeight((int) (startValue + deltaValue * interpolatedTime));
				}
			};
			animation.setDuration(durationMillis);
			tv_content.startAnimation(animation);
		}
	};


下面是展开/收起朋友圈动态详情的效果截图
技术分享



可折叠列表ExpandableListView

嵌套列表ExpandableListView是又一种常见的控件,常见的业务场景包括:好友分组与好友列表、订单列表与订单内的商品列表、邮件夹分组与邮件列表等等。


ExpandableListView常用方法

Android自带的ExpandableListView可以直接用于嵌套列表,点击一个组,展开该组下的子列表;再点击这个组,收起该组下的子列表。

下面是ExpandableListView的常用方法说明:
setAdapter : 设置适配器。适配器类型为ExpandableListAdapter
expandGroup : 展开指定分组。
collapseGroup : 收起指定分组。
isGroupExpanded : 判断指定分组是否展开。
setSelectedGroup : 设置选中的分组。
setSelectedChild : 设置选中的子项。
setGroupIndicator : 设置指定分组的指示图像。
setChildIndicator : 设置指定子项的指示图像。


ExpandableListView监听器

除了OnItemClickListener,ExpandableListView新加了下面几个监听器:

1、分组展开事件,相关类名与方法说明如下:
监听器类名 : OnGroupExpandListener
设置监听器的方法 : setOnGroupExpandListener
监听器需要重写的点击方法 : onGroupExpand

2、分组收起事件,相关类名与方法说明如下:
监听器类名 : OnGroupCollapseListener
设置监听器的方法 : setOnGroupCollapseListener
监听器需要重写的点击方法 : onGroupCollapse
3、分组点击事件,相关类名与方法说明如下:
监听器类名 : OnGroupClickListener
设置监听器的方法 : setOnGroupClickListener
监听器需要重写的点击方法 : onGroupClick

4、子项点击事件,相关类名与方法说明如下:
监听器类名 : OnChildClickListener
设置监听器的方法 : setOnChildClickListener
监听器需要重写的点击方法 : onChildClick


ExpandableListView适配器

ExpandableListAdapter是ExpandableListView的专用适配器,它并不继承自其他适配器。

下面是ExpandableListAdapter经常要重写的几个方法:
getGroupCount : 获取分组的个数。
getChildrenCount : 获取子项的个数。
getGroupView : 获取指定分组的视图。
getChildView : 获取指定子项的视图。
isChildSelectable : 判断子项是否允许选择。


ExpandableListView常见问题

ExpandableListView有时会发现子项不会响应点击事件,这可能是某个环节没有正确设置。要让子项目响应点击事件,需满足下面三个条件:
1、ExpandableListAdapter适配器的isChildSelectable方法要返回true;
2、ExpandableListView对象要注册监听器setOnChildClickListener,并重写onChildClick方法;
3、子项目中若有Button、EditText等默认占用焦点的控件,要去除焦点占用,即setFocusable和setFocusableInTouchMode设置为false;


下面是ExpandableListView的一个应用例子效果截图(电子邮箱):
技术分享


下面是运用ExpandableListView的代码示例:
适配器代码
import java.util.ArrayList;

import com.example.exmfoldlist.R;
import com.example.exmfoldlist.bean.MailBox;
import com.example.exmfoldlist.bean.MailItem;

import android.content.Context;
import android.database.DataSetObserver;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class CustomExpandAdapter implements ExpandableListAdapter,OnGroupClickListener,OnChildClickListener {

	private final static String TAG = "CustomExpandAdapter";
	private LayoutInflater mInflater;
	private Context mContext;
	private ArrayList<MailBox> mBoxList;

	public CustomExpandAdapter(Context context, ArrayList<MailBox> box_list) {
		mInflater = LayoutInflater.from(context);
		mContext = context;
		mBoxList = box_list;
	}

	@Override
	public void registerDataSetObserver(DataSetObserver observer) {
	}

	@Override
	public void unregisterDataSetObserver(DataSetObserver observer) {
	}

	@Override
	public int getGroupCount() {
		return mBoxList.size();
	}

	@Override
	public int getChildrenCount(int groupPosition) {
		return mBoxList.get(groupPosition).mail_list.size();
	}

	@Override
	public Object getGroup(int groupPosition) {
		return mBoxList.get(groupPosition);
	}

	@Override
	public Object getChild(int groupPosition, int childPosition) {
		return mBoxList.get(groupPosition).mail_list.get(childPosition);
	}

	@Override
	public long getGroupId(int groupPosition) {
		return groupPosition;
	}

	@Override
	public long getChildId(int groupPosition, int childPosition) {
		return childPosition;
	}

	@Override
	public boolean hasStableIds() {
		return false;
	}

	@Override
	public View getGroupView(int groupPosition, boolean isExpanded,
			View convertView, ViewGroup parent) {
		ViewHolderBox holder = null;
		if (convertView == null) {
			holder = new ViewHolderBox();
			convertView = mInflater.inflate(R.layout.list_box, null);
			holder.iv_box = (ImageView) convertView.findViewById(R.id.iv_box);
			holder.tv_box = (TextView) convertView.findViewById(R.id.tv_box);
			holder.tv_count = (TextView) convertView.findViewById(R.id.tv_count);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolderBox) convertView.getTag();
		}
		MailBox box = mBoxList.get(groupPosition);
		holder.iv_box.setImageResource(box.box_icon);
		holder.tv_box.setText(box.box_title);
		holder.tv_count.setText(box.mail_list.size()+"封邮件");
		return convertView;
	}

	@Override
	public View getChildView(final int groupPosition, final int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent) {
		ViewHolderMail holder = null;
		if (convertView == null) {
			holder = new ViewHolderMail();
			convertView = mInflater.inflate(R.layout.list_mail, null);
			holder.ck_mail = (CheckBox) convertView.findViewById(R.id.ck_mail);
			holder.tv_date = (TextView) convertView.findViewById(R.id.tv_date);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolderMail) convertView.getTag();
		}
		MailItem item = mBoxList.get(groupPosition).mail_list.get(childPosition);
		holder.ck_mail.setFocusable(false);
		holder.ck_mail.setFocusableInTouchMode(false);
		holder.ck_mail.setText(item.mail_title);
		holder.ck_mail.setOnCheckedChangeListener(new OnCheckedChangeListener() {
			@Override
			public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
				MailBox box = mBoxList.get(groupPosition);
				MailItem item = box.mail_list.get(childPosition);
				String desc = String.format("您点击了%s的邮件,标题是%s", box.box_title, item.mail_title);
				Toast.makeText(mContext, desc, Toast.LENGTH_LONG).show();
			}
		});
		holder.tv_date.setText(item.mail_date);
		return convertView;
	}

	//如果子条目需要响应点击事件,这里要返回true
	@Override
	public boolean isChildSelectable(int groupPosition, int childPosition) {
		return true;
	}

	@Override
	public boolean areAllItemsEnabled() {
		return true;
	}

	@Override
	public boolean isEmpty() {
		return false;
	}

	@Override
	public void onGroupExpanded(int groupPosition) {
	}

	@Override
	public void onGroupCollapsed(int groupPosition) {
	}

	@Override
	public long getCombinedChildId(long groupId, long childId) {
		return 0;
	}

	@Override
	public long getCombinedGroupId(long groupId) {
		return 0;
	}

	public final class ViewHolderBox {
		public ImageView iv_box;
		public TextView tv_box;
		public TextView tv_count;
	}

	public final class ViewHolderMail {
		public CheckBox ck_mail;
		public TextView tv_date;
	}

	@Override
	public boolean onChildClick(ExpandableListView parent, View v,
			int groupPosition, int childPosition, long id) {
		ViewHolderMail holder = (ViewHolderMail) v.getTag();
		holder.ck_mail.setChecked(!(holder.ck_mail.isChecked()));
		return true;
	}

	//如果返回true,就不会展示子列表
	@Override
	public boolean onGroupClick(ExpandableListView parent, View v,
			int groupPosition, long id) {
		String desc = String.format("您点击了%s", mBoxList.get(groupPosition).box_title);
		Toast.makeText(mContext, desc, Toast.LENGTH_LONG).show();
		return false;
	}

}


调用的代码
import java.util.ArrayList;

import com.example.exmfoldlist.adapter.CustomExpandAdapter;
import com.example.exmfoldlist.bean.MailBox;
import com.example.exmfoldlist.bean.MailItem;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ExpandableListView;

public class ExpandActivity extends Activity {
	private final static String TAG = "ExpandActivity";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_expand);
		
		ExpandableListView elv_list = (ExpandableListView) findViewById(R.id.elv_list);
		final ArrayList<MailBox> box_list = new ArrayList<MailBox>();
		box_list.add(new MailBox(R.drawable.mail_folder_inbox, "收件箱", getRecvMail()));
		box_list.add(new MailBox(R.drawable.mail_folder_outbox, "发件箱", getSentMail()));
		box_list.add(new MailBox(R.drawable.mail_folder_draft, "草稿箱", getDraftMail()));
		box_list.add(new MailBox(R.drawable.mail_folder_recycle, "废件箱", getRecycleMail()));
		CustomExpandAdapter adapter = new CustomExpandAdapter(this, box_list);
		elv_list.setAdapter(adapter);
		elv_list.setOnChildClickListener(adapter);
		elv_list.setOnGroupClickListener(adapter);
		elv_list.expandGroup(0);  //默认展开第一个邮件夹
	}
	
	private ArrayList<MailItem> getRecvMail() {
		ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
		mail_list.add(new MailItem("这里是收件箱呀1", "2016年3月25日"));
		mail_list.add(new MailItem("这里是收件箱呀2", "2016年3月20日"));
		mail_list.add(new MailItem("这里是收件箱呀3", "2016年3月24日"));
		mail_list.add(new MailItem("这里是收件箱呀4", "2016年3月21日"));
		mail_list.add(new MailItem("这里是收件箱呀5", "2016年3月23日"));
		return mail_list;
	}

	private ArrayList<MailItem> getSentMail() {
		ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
		mail_list.add(new MailItem("邮件发出去了吗1", "2016年3月25日"));
		mail_list.add(new MailItem("邮件发出去了吗2", "2016年3月24日"));
		mail_list.add(new MailItem("邮件发出去了吗3", "2016年3月21日"));
		mail_list.add(new MailItem("邮件发出去了吗4", "2016年3月23日"));
		mail_list.add(new MailItem("邮件发出去了吗5", "2016年3月20日"));
		return mail_list;
	}

	private ArrayList<MailItem> getDraftMail() {
		ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
		mail_list.add(new MailItem("暂时放在草稿箱吧1", "2016年3月24日"));
		mail_list.add(new MailItem("暂时放在草稿箱吧2", "2016年3月21日"));
		mail_list.add(new MailItem("暂时放在草稿箱吧3", "2016年3月25日"));
		mail_list.add(new MailItem("暂时放在草稿箱吧4", "2016年3月20日"));
		mail_list.add(new MailItem("暂时放在草稿箱吧5", "2016年3月23日"));
		return mail_list;
	}

	private ArrayList<MailItem> getRecycleMail() {
		ArrayList<MailItem> mail_list = new ArrayList<MailItem>();
		mail_list.add(new MailItem("啊啊啊,怎么被删除了1", "2016年3月21日"));
		mail_list.add(new MailItem("啊啊啊,怎么被删除了2", "2016年3月23日"));
		mail_list.add(new MailItem("啊啊啊,怎么被删除了3", "2016年3月25日"));
		mail_list.add(new MailItem("啊啊啊,怎么被删除了4", "2016年3月20日"));
		mail_list.add(new MailItem("啊啊啊,怎么被删除了5", "2016年3月24日"));
		return mail_list;
	}

}


折叠式布局FoldingLayout

ExpandableListView对于一般场景的折叠式列表已经够用了,可是它的UI风格略显呆板,如果我们想来个显示特效,比如加上折叠展开的动画,那最好还是自己写个折叠式列表控件。


FoldingLayout便是这样一个开源的折叠式布局控件,它实现了像折纸那样折叠展开和折叠收起的动画。下面是FoldingLayout的常用方法说明:
setFoldFactor : 设置折叠的因子。0表示收起,1表示展开。
setOrientation : 设置折叠的方向。VERTICAL表示垂直方向,HORIZONTAL表示水平方向。
setNumberOfFolds : 设置折叠的页数。


FoldingLayout也提供了折叠事件的监听,相关类名与方法说明如下:
监听器类名 : OnFoldListener
设置监听器的方法 : setFoldListener
监听器需要重写的点击方法 : 
onStartFold : 开始折叠时触发。
onFoldingState : 折叠状态变化时触发。
onEndFold : 结束折叠时触发。


下面是运用FoldingLayout的代码示例:
import com.example.exmfoldlist.util.MetricsUtil;
import com.example.exmfoldlist.view.FoldingLayout;
import com.example.exmfoldlist.view.OnFoldListener;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

public class FoldingActivity extends Activity {
    private String TAG_ARROW = "service_arrow";
    private String TAG_ITEM = "service_item";
    
    private View mBottomView;
    private LinearLayout mTrafficLayout, mLifeLayout, mMedicalLayout, mLiveLayout, mPublicLayout;
    private RelativeLayout mTrafficBarLayout, mLifeBarLayout, mMedicalBarLayout, mLiveBarLayout, mPublicBarLayout;
    private FoldingLayout mTrafficFoldingLayout, mLifeFoldingLayout, mMedicalFoldingLayout, mLiveFoldingLayout, mPublicFoldingLayout;
    
    private final int FOLD_ANIMATION_DURATION = 1000;
    private int mNumberOfFolds = 3;
    private Handler mHandler = new Handler();

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_folding);

        mTrafficLayout = (LinearLayout) findViewById(R.id.traffic_layout);
        mLifeLayout = (LinearLayout) findViewById(R.id.life_layout);
        mMedicalLayout = (LinearLayout) findViewById(R.id.medical_layout);
        mLiveLayout = (LinearLayout) findViewById(R.id.live_layout);
        mPublicLayout = (LinearLayout) findViewById(R.id.public_layout);
                
        mTrafficBarLayout = (RelativeLayout) findViewById(R.id.traffic_bar_layout);
        mLifeBarLayout = (RelativeLayout) findViewById(R.id.life_bar_layout);
        mMedicalBarLayout = (RelativeLayout) findViewById(R.id.medical_bar_layout);
        mLiveBarLayout = (RelativeLayout) findViewById(R.id.live_bar_layout);
        mPublicBarLayout = (RelativeLayout) findViewById(R.id.public_bar_layout);
        
        mTrafficFoldingLayout = ((FoldingLayout) mTrafficLayout.findViewWithTag(TAG_ITEM));
        mLifeFoldingLayout = ((FoldingLayout) mLifeLayout.findViewWithTag(TAG_ITEM));
        mMedicalFoldingLayout = ((FoldingLayout) mMedicalLayout.findViewWithTag(TAG_ITEM));
        mLiveFoldingLayout = ((FoldingLayout) mLiveLayout.findViewWithTag(TAG_ITEM));
        mPublicFoldingLayout = ((FoldingLayout) mPublicLayout.findViewWithTag(TAG_ITEM));

        mBottomView = findViewById(R.id.bottom_view);
        initFoldingLayout(mTrafficFoldingLayout, mTrafficBarLayout, mTrafficLayout, mLifeLayout);
        initFoldingLayout(mLifeFoldingLayout, mLifeBarLayout, mLifeLayout, mMedicalLayout);
        initFoldingLayout(mMedicalFoldingLayout, mMedicalBarLayout, mMedicalLayout, mLiveLayout);
        initFoldingLayout(mLiveFoldingLayout, mLiveBarLayout, mLiveLayout, mPublicLayout);
        initFoldingLayout(mPublicFoldingLayout, mPublicBarLayout, mPublicLayout, mBottomView);
        setBarEnabled(false);
        mHandler.postDelayed(mDefaultFold, 150);
    }
    
    private void initFoldingLayout(final FoldingLayout foldingLayout, View bar, final View thisView, final View nextView) {
    	foldingLayout.setVisibility(View.INVISIBLE);
    	bar.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                handleAnimation(v, foldingLayout, thisView, nextView);
            }
        });
    	foldingLayout.setNumberOfFolds(mNumberOfFolds);
    	animateFold(foldingLayout, 100);
    	setMarginToTop(1, nextView);
    }
    
    private void setBarEnabled(boolean enabled) {
        mTrafficBarLayout.setEnabled(enabled);
        mLifeBarLayout.setEnabled(enabled);
        mMedicalBarLayout.setEnabled(enabled);
        mLiveBarLayout.setEnabled(enabled);
        mPublicBarLayout.setEnabled(enabled);
        mTrafficFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
        mLifeFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
        mMedicalFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
        mLiveFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
        mPublicFoldingLayout.setFoldFactor(!enabled?0.0f:1.0f);
    }
    
    private Runnable mDefaultFold = new Runnable() {
		@Override
		public void run() {
	        setBarEnabled(true);
	        handleAnimation(mTrafficBarLayout, mTrafficFoldingLayout, mTrafficLayout, mLifeLayout);
		}
    };
    
    private void handleAnimation(final View bar, final FoldingLayout foldingLayout, View parent, final View nextParent) {
    	foldingLayout.setVisibility(View.VISIBLE);
        final ImageView arrow = (ImageView) parent.findViewWithTag(TAG_ARROW);
        
        foldingLayout.setFoldListener(new OnFoldListener() {
            @Override
            public void onStartFold(float foldFactor) {
                bar.setClickable(true);
                arrow.setBackgroundResource(R.drawable.service_arrow_up);
                resetMarginToTop(foldingLayout, foldFactor, nextParent);
            }
            
            @Override
            public void onFoldingState(float foldFactor, float foldDrawHeight) {
                bar.setClickable(false);
                resetMarginToTop(foldingLayout, foldFactor, nextParent);
            }
            
            @Override
            public void onEndFold(float foldFactor) {
                bar.setClickable(true);
                arrow.setBackgroundResource(R.drawable.service_arrow_down);
                resetMarginToTop(foldingLayout, foldFactor, nextParent);
            }
        });
        animateFold(foldingLayout, FOLD_ANIMATION_DURATION);
    }
    
    private void resetMarginToTop(View view, float foldFactor, View nextParent) {
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) nextParent.getLayoutParams();
        lp.topMargin =(int)( - view.getMeasuredHeight() * foldFactor) + MetricsUtil.dip2px(FoldingActivity.this, 10);
        nextParent.setLayoutParams(lp);
    }
    
    private void setMarginToTop(float foldFactor, View nextParent) {
        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) nextParent.getLayoutParams();
        lp.topMargin =(int)( - MetricsUtil.dip2px(FoldingActivity.this, 135) * foldFactor) + MetricsUtil.dip2px(FoldingActivity.this, 10);
        nextParent.setLayoutParams(lp);
    }
    
    public void animateFold(FoldingLayout foldLayout, int duration) {
        float foldFactor = foldLayout.getFoldFactor();
        ObjectAnimator animator = ObjectAnimator.ofFloat(foldLayout,
                "foldFactor", foldFactor, foldFactor > 0 ? 0 : 1);
        animator.setRepeatMode(ValueAnimator.REVERSE);
        animator.setRepeatCount(0);
        animator.setDuration(duration);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.start();
    }
    
}




点此查看Android开发笔记的完整目录

Android开发笔记(一百)折叠式列表

标签:

原文地址:http://blog.csdn.net/aqi00/article/details/51381611

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!