前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(一百)折叠式列表

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

作者头像
aqi00
发布2019-01-18 14:04:51
2.1K0
发布2019-01-18 14:04:51
举报
文章被收录于专栏:老欧说安卓老欧说安卓

更多动态视图MoreNewsView

经常看朋友圈的动态,有的动态内容较多就只展示前面一段,如果用户想看完整的再点击展开,这样整个页面的动态列表比较均衡,不会出现个别动态占用大片屏幕的情况。同样,查看博客的文章列表也类似,只展示文章开头几行内容,有需要再点击加载全篇文章。 动态列表直接使用ListView,动态内容就得自己写个控件了,自定义控件的难点在于如何把握动态下拉和收起的动画。这里我们要先预习TextView的相关函数,下面是本文用到的方法说明: getHeight : 获取TextView的显示高度。 setHeight : 设置TextView的显示高度。 getLineHeight : 获取每行文本的高度。 getLineCount : 获取所有文本的行数。 如果一开始每条动态默认显示四行,那么默认显示高度是getLineHeight*4,使用setHeight方法即可设置动态的初始显示高度。点击展开动态全文时,就得显示所有行的文本,整个文本的高度是getLineHeight*getLineCount。现在有了每条动态的初始高度,以及动态全文的完整高度,再加个拉伸动画就差不多了。拉伸动画的主要工作是随着时间的推移,给TextView设置渐增或渐减的高度,这要重写Animation的applyTransformation方法。 下面是点击监听器的显示动画代码示例:

代码语言:javascript
复制
	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的代码示例: 适配器代码

代码语言:javascript
复制
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;
	}

}

调用的代码

代码语言:javascript
复制
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的代码示例:

代码语言:javascript
复制
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开发笔记的完整目录

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年05月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 更多动态视图MoreNewsView
  • 可折叠列表ExpandableListView
    • ExpandableListView常用方法
      • ExpandableListView监听器
        • ExpandableListView适配器
          • ExpandableListView常见问题
          • 折叠式布局FoldingLayout
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档