本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈
轉載請註明出處: http://blog.csdn.net/Chay_Chan/article/details/72810770java
##ExpandableLinearLayout介紹
###場景介紹
開發的過程當中,有時咱們須要使用到這樣一個功能,在展現一些商品的時候,默認只顯示前幾個,例如先顯示前三個,這樣子不會一進入頁面就被商品列表佔據了大部分,能夠先讓用戶能夠看到頁面的大概,當用戶須要查看更多的商品時,點擊「展開」,就能夠看到被隱藏的商品,點擊「收起」,則又回到一開始的狀態,只顯示前幾個,其餘的收起來了。就拿美團外賣的訂單詳情頁的佈局做爲例子,請看如下圖片:android
訂單詳情頁面一開始只顯示購買的前三樣菜,當點擊「點擊展開」時,則將購買的全部外賣都展現出來,當點擊「點擊收起」時,則將除了前三樣菜之外的都隱藏起來。其實要完成這樣的功能並不難,爲了方便本身和你們之後的開發,我將其封裝成一個控件,取名爲ExpandableLinearLayout,下面開始介紹它如何使用以及源碼解析。git
##使用方式
###1、使用默認展開和收起的底部
在佈局文件中,使用ExpandableLinearLayout,代碼以下:github
<com.chaychan.viewlib.ExpandableLinearLayout android:id="@+id/ell_product" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:orientation="vertical" app:useDefaultBottom="true" app:defaultItemCount="2" app:expandText="點擊展開" app:hideText="點擊收起" ></com.chaychan.viewlib.ExpandableLinearLayout>
和LinearLayout的使用方法相似,若是是靜態數據,能夠在兩個標籤中間插入子條目佈局的代碼,也能夠在java文件中使用代碼動態插入。useDefaultBottom是指是否使用默認底部(默認爲true,若是須要使用默認底部,可不寫這個屬性),若是是自定義的底部,則設置爲false,下面會介紹自定義底部的用法,defaultItemCount=「2」,設置默認顯示的個數爲2,expandText爲待展開時的文字提示,hideText爲待收起時的文字提示。web
在java文件中,根據id找到控件,動態往ExpandableLinearLayout中插入子條目並設置數據便可,代碼以下:微信
@Bind(R.id.ell_product) ExpandableLinearLayout ellProduct; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.page_ell_default_bottom_demo); ButterKnife.bind(this); ellProduct.removeAllViews();//清除全部的子View(避免從新刷新數據時重複添加) //添加數據 for (int i = 0; i < 5; i++) { View view = View.inflate(this, R.layout.item_product, null); ProductBean productBean = new ProductBean(imgUrls[i], names[i], intros[i], "12.00"); ViewHolder viewHolder = new ViewHolder(view, productBean); viewHolder.refreshUI(); ellProduct.addItem(view);//添加子條目 } } class ViewHolder { @Bind(R.id.iv_img) ImageView ivImg; @Bind(R.id.tv_name) TextView tvName; @Bind(R.id.tv_intro) TextView tvIntro; @Bind(R.id.tv_price) TextView tvPrice; ProductBean productBean; public ViewHolder(View view, ProductBean productBean) { ButterKnife.bind(this, view); this.productBean = productBean; } private void refreshUI() { Glide.with(EllDefaultBottomDemoActivity.this) .load(productBean.getImg()) .placeholder(R.mipmap.ic_default) .into(ivImg); tvName.setText(productBean.getName()); tvIntro.setText(productBean.getIntro()); tvPrice.setText("¥" + productBean.getPrice()); } }
效果以下:app
####1.支持修改默認顯示的個數
能夠修改默認顯示的個數,好比將其修改成3,即defaultItemCount=「3」maven
效果以下:ide
####2.支持修改待展開和待收起狀態下的文字提示
能夠修改待展開狀態和待收起狀態下的文字提示,好比修改expandText=「查看更多」,hideText=「收起更多」svg
效果以下:
####3.支持修改提示文字的大小、顏色
能夠修改提示文字的大小和顏色,對應的屬性分別是tipTextSize,tipTextColor。好比修改tipTextSize=「16sp」,tipTextColor="#ff7300"
效果以下:
####4.支持更換箭頭的圖標
能夠修改箭頭的圖標,只需配置arrowDownImg屬性,引用對應的圖標,這裏的箭頭圖標須要是向下的箭頭,這樣當展開和收起時,箭頭會作相應的旋轉動畫。設置arrowDownImg="@mipmap/arrow_down_grey",修改成灰色的向下圖標。
效果以下:
###2、使用自定義底部
佈局文件中,ExpandableLinearLayout配置useDefaultBottom=「false」,聲明不使用默認底部。本身定義底部的佈局。
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <!--商品列表--> <com.chaychan.viewlib.ExpandableLinearLayout android:id="@+id/ell_product" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:orientation="vertical" app:defaultItemCount="2" app:useDefaultBottom="false" > </com.chaychan.viewlib.ExpandableLinearLayout> <!--自定義底部--> <RelativeLayout...> <!--優惠、實付款--> <RelativeLayout...> </LinearLayout> </ScrollView>
java文件中,代碼以下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.page_ell_custom_bottom_demo); ButterKnife.bind(this); ... //插入模擬數據的代碼,和上面演示使用默認底部的代碼同樣 //設置狀態改變時的回調 ellProduct.setOnStateChangeListener(new ExpandableLinearLayout.OnStateChangeListener() { @Override public void onStateChanged(boolean isExpanded) { doArrowAnim(isExpanded);//根據狀態箭頭旋轉 //根據狀態更改文字提示 if (isExpanded) { //展開 tvTip.setText("點擊收起"); } else { tvTip.setText("點擊展開"); } } }); //爲自定義的底部設置點擊事件 rlBottom.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ellProduct.toggle(); } }); } // 箭頭的動畫 private void doArrowAnim(boolean isExpand) { if (isExpand) { // 當前是展開,箭頭由下變爲上 ObjectAnimator.ofFloat(ivArrow, "rotation", 0, 180).start(); } else { // 當前是收起,箭頭由上變爲下 ObjectAnimator.ofFloat(ivArrow, "rotation", -180, 0).start(); } }
主要的代碼是爲ExpandableLinearLayout設置狀態改變的回調,rlBottom爲自定義底部的根佈局RelativeLayout,爲其設置點擊事件,當點擊的時候調用ExpandableLinearLayout的toggle()方法,當收到回調時,根據狀態旋轉箭頭以及更改文字提示。
效果以下:
到這裏,ExpandableLinearLayout的使用就介紹完畢了,接下來是對源碼進行解析。
##源碼解析
ExpandableLinearLayout的原理其實很簡單,當使用默認的底部時,若是子條目的個數小於或者等於默認顯示的個數,則不添加底部,若是子條目的個數大於默認顯示的個數,則往最後插入一個默認的底部,一開始的時候,將ExpandableLinearLayout除了默認顯示的條目和底部不隱藏之外,其餘的子條目都進行隱藏,當點擊「展開」的時候,將被隱藏的條目設置爲顯示狀態,當點擊「收起」的時候,將默認顯示條目如下的那些條目都隱藏。
首先介紹下ExpandableLinearLayout自定義的屬性:
<declare-styleable name="ExpandableLinearLayout"> <!--默認顯示的條目數--> <attr name="defaultItemCount" format="integer" /> <!--提示文字的大小--> <attr name="tipTextSize" format="dimension" /> <!--字體顏色--> <attr name="tipTextColor" format="color"/> <!--待展開的文字提示--> <attr name="expandText" format="string" /> <!--待收起時的文字提示--> <attr name="hideText" format="string" /> <!--向下的箭頭的圖標--> <attr name="arrowDownImg" format="reference" /> <!--是否使用默認的底部--> <attr name="useDefaultBottom" format="boolean" /> </declare-styleable>
ExpandableLinearLayout繼承於LinearLayout
public class ExpandableLinearLayout extends LinearLayout implements View.OnClickListener { public ExpandableLinearLayout(Context context) { this(context, null); } public ExpandableLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ExpandableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //獲取自定義屬性的值 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandableLinearLayout); defaultItemCount = ta.getInt(R.styleable.ExpandableLinearLayout_defaultItemCount, 2); expandText = ta.getString(R.styleable.ExpandableLinearLayout_expandText); hideText = ta.getString(R.styleable.ExpandableLinearLayout_hideText); fontSize = ta.getDimension(R.styleable.ExpandableLinearLayout_tipTextSize, UIUtils.sp2px(context, 14)); textColor = ta.getColor(R.styleable.ExpandableLinearLayout_tipTextColor, Color.parseColor("#666666")); arrowResId = ta.getResourceId(R.styleable.ExpandableLinearLayout_arrowDownImg, R.mipmap.arrow_down); useDefaultBottom = ta.getBoolean(R.styleable.ExpandableLinearLayout_useDefaultBottom, true); ta.recycle(); setOrientation(VERTICAL); } /** * 渲染完成時初始化默認底部view */ @Override protected void onFinishInflate() { super.onFinishInflate(); findViews(); } /** * 初始化底部view */ private void findViews() { bottomView = View.inflate(getContext(), R.layout.item_ell_bottom, null); ivArrow = (ImageView) bottomView.findViewById(R.id.iv_arrow); tvTip = (TextView) bottomView.findViewById(R.id.tv_tip); tvTip.getPaint().setTextSize(fontSize); tvTip.setTextColor(textColor); ivArrow.setImageResource(arrowResId); bottomView.setOnClickListener(this); }
}
添加子條目的方法,addItem(View view):
public void addItem(View view) { int childCount = getChildCount(); if (!useDefaultBottom){ //若是不使用默認底部 addView(view); if (childCount > defaultItemCount){ hide(); } return; } //使用默認底部 if (!hasBottom) { //若是尚未底部 addView(view); } else { addView(view, childCount - 2);//插在底部以前 } refreshUI(view); }
當添加條目的時候,獲取全部子條目的個數,若是是不使用默認底部的話,則只是將View添加到ExpandableLinearLayout中,當數目超過默認顯示個數時,則調用hide()方法,收起除了默認顯示條目外的其餘條目,即將它們設置爲隱藏。若是是使用默認底部,hasBottom爲是否已經有底部的標誌,若是尚未底部則是直接往ExpandableLinearLayout中順序添加,若是已經有底部,則是往底部前一個的位置添加View。調用的相關方法代碼以下:
/** * 收起 */ private void hide() { int endIndex = useDefaultBottom ? getChildCount() - 1 : getChildCount();//若是是使用默認底部,則結束的下標是到底部以前,不然則所有子條目都隱藏 for (int i = defaultItemCount; i < endIndex; i++) { //從默認顯示條目位置如下的都隱藏 View view = getChildAt(i); view.setVisibility(GONE); } } /** * 刷新UI * * @param view */ private void refreshUI(View view) { int childCount = getChildCount(); if (childCount > defaultItemCount) { if (childCount - defaultItemCount == 1) { //剛超過默認,判斷是否要添加底部 justToAddBottom(childCount); } view.setVisibility(GONE);//大於默認數目的先隱藏 } } /** * 判斷是否要添加底部 * @param childCount */ private void justToAddBottom(int childCount) { if (childCount > defaultItemCount) { if (useDefaultBottom && !hasBottom) { //要使用默認底部,而且尚未底部 addView(bottomView);//添加底部 hide(); hasBottom = true; } } }
默認底部的點擊事件:
@Override public void onClick(View v) { toggle(); } public void toggle() { if (isExpand) { hide(); tvTip.setText(expandText); } else { expand(); tvTip.setText(hideText); } doArrowAnim(); isExpand = !isExpand; //回調 if (mListener != null){ mListener.onStateChanged(isExpand); } }
點擊的時候調用toggle()會根據當前狀態,進行展開或收起,若是當前是展開狀態,即isExpand爲true,則調用hide()方法收起,不然,當前是收起狀態時,調用 expand( )進行展開。這裏判斷若是有設置狀態改變的監聽,若是有則調用接口的方法將狀態傳遞出去,expand( )方法的代碼以下:
/** * 展開 */ private void expand() { for (int i = defaultItemCount; i < getChildCount(); i++) { //從默認顯示條目位置如下的都顯示出來 View view = getChildAt(i); view.setVisibility(VISIBLE); } }
到這裏爲止,ExpandableLinearLayout的源碼解析就結束了,但願能夠這個控件能夠幫助到你們。
在項目根目錄下的build.gradle中的allprojects{}中,添加jitpack倉庫地址,以下:
allprojects { repositories { jcenter() maven { url 'https://jitpack.io' }//添加jitpack倉庫地址 } }
打開app的module中的build.gradle,在dependencies{}中,添加依賴,以下:
dependencies { compile 'com.github.chaychan:ExpandableLinearLayout:1.0.0' }
源碼github地址:https://github.com/chaychan/ExpandableLinearLayout
同時也收錄在PowfulViewLibrary中,若是想要在PowfulViewLibrary也有這個控件,更新下PowfulViewLibrary的版本。如下版本爲目前最新:
compile ‘com.github.chaychan:PowerfulViewLibrary:1.1.6’
PowerfulViewLibrary源碼地址: https://github.com/chaychan/PowerfulViewLibrary