Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發筆記(一百)折疊式列表

Android開發筆記(一百)折疊式列表

編輯:關於Android編程

更多動態視圖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 mBoxList;

	public CustomExpandAdapter(Context context, ArrayList 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 box_list = new ArrayList();
		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 getRecvMail() {
		ArrayList mail_list = new ArrayList();
		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 getSentMail() {
		ArrayList mail_list = new ArrayList();
		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 getDraftMail() {
		ArrayList mail_list = new ArrayList();
		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 getRecycleMail() {
		ArrayList mail_list = new ArrayList();
		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();
    }
    
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved