ExpandableLinearLayout介紹
場景介紹
??開發(fā)的過程中葛圃,有時我們需要使用到這樣一個功能舔腾,在展示一些商品的時候,默認只顯示前幾個阔拳,例如先顯示前三個,這樣子不會一進入頁面就被商品列表占據(jù)了大部分类嗤,可以先讓用戶可以看到頁面的大概糊肠,當用戶需要查看更多的商品時辨宠,點擊“展開”,就可以看到被隱藏的商品货裹,點擊“收起”嗤形,則又回到一開始的狀態(tài),只顯示前幾個弧圆,其他的收起來了赋兵。就拿美團外賣的訂單詳情頁的布局作為例子,請看以下圖片:
??訂單詳情頁面一開始只顯示購買的前三樣菜搔预,當點擊“點擊展開”時霹期,則將購買的所有外賣都展示出來,當點擊“點擊收起”時斯撮,則將除了前三樣菜以外的都隱藏起來经伙。其實要完成這樣的功能并不難扶叉,為了方便自己和大家以后的開發(fā)勿锅,我將其封裝成一個控件,取名為ExpandableLinearLayout枣氧,下面開始介紹它如何使用以及源碼解析溢十。
使用方式
一、使用默認展開和收起的底部
在布局文件中达吞,使用ExpandableLinearLayout张弛,代碼如下:
<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的使用方法類似,如果是靜態(tài)數(shù)據(jù)酪劫,可以在兩個標簽中間插入子條目布局的代碼吞鸭,也可以在java文件中使用代碼動態(tài)插入。useDefaultBottom是指是否使用默認底部(默認為true覆糟,如果需要使用默認底部刻剥,可不寫這個屬性),如果是自定義的底部滩字,則設(shè)置為false造虏,下面會介紹自定義底部的用法,defaultItemCount="2",設(shè)置默認顯示的個數(shù)為2麦箍,expandText為待展開時的文字提示漓藕,hideText為待收起時的文字提示。
在java文件中挟裂,根據(jù)id找到控件享钞,動態(tài)往ExpandableLinearLayout中插入子條目并設(shè)置數(shù)據(jù)即可,代碼如下:
@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(避免重新刷新數(shù)據(jù)時重復(fù)添加)
//添加數(shù)據(jù)
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());
}
}
效果如下:
1.支持修改默認顯示的個數(shù)
可以修改默認顯示的個數(shù)诀蓉,比如將其修改為3栗竖,即defaultItemCount="3"
效果如下:
2.支持修改待展開和待收起狀態(tài)下的文字提示
可以修改待展開狀態(tài)和待收起狀態(tài)下的文字提示寝姿,比如修改expandText="查看更多",hideText="收起更多"
效果如下:
3.支持修改提示文字的大小划滋、顏色
可以修改提示文字的大小和顏色饵筑,對應(yīng)的屬性分別是tipTextSize,tipTextColor处坪。比如修改tipTextSize="16sp"根资,tipTextColor="#ff7300"
效果如下:
4.支持更換箭頭的圖標
可以修改箭頭的圖標,只需配置arrowDownImg屬性同窘,引用對應(yīng)的圖標玄帕,這里的箭頭圖標需要是向下的箭頭,這樣當展開和收起時想邦,箭頭會做相應(yīng)的旋轉(zhuǎn)動畫裤纹。設(shè)置arrowDownImg="@mipmap/arrow_down_grey",修改為灰色的向下圖標丧没。
效果如下:
二鹰椒、使用自定義底部
布局文件中,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...>
<!--優(yōu)惠、實付款-->
<RelativeLayout...>
</LinearLayout>
</ScrollView>
java文件中夺饲,代碼如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.page_ell_custom_bottom_demo);
ButterKnife.bind(this);
... //插入模擬數(shù)據(jù)的代碼奸汇,和上面演示使用默認底部的代碼一樣
//設(shè)置狀態(tài)改變時的回調(diào)
ellProduct.setOnStateChangeListener(new ExpandableLinearLayout.OnStateChangeListener() {
@Override
public void onStateChanged(boolean isExpanded) {
doArrowAnim(isExpanded);//根據(jù)狀態(tài)箭頭旋轉(zhuǎn)
//根據(jù)狀態(tài)更改文字提示
if (isExpanded) {
//展開
tvTip.setText("點擊收起");
} else {
tvTip.setText("點擊展開");
}
}
});
//為自定義的底部設(shè)置點擊事件
rlBottom.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ellProduct.toggle();
}
});
}
// 箭頭的動畫
private void doArrowAnim(boolean isExpand) {
if (isExpand) {
// 當前是展開,箭頭由下變?yōu)樯? ObjectAnimator.ofFloat(ivArrow, "rotation", 0, 180).start();
} else {
// 當前是收起往声,箭頭由上變?yōu)橄? ObjectAnimator.ofFloat(ivArrow, "rotation", -180, 0).start();
}
}
主要的代碼是為ExpandableLinearLayout設(shè)置狀態(tài)改變的回調(diào)擂找,rlBottom為自定義底部的根布局RelativeLayout,為其設(shè)置點擊事件,當點擊的時候調(diào)用ExpandableLinearLayout的toggle()方法浩销,當收到回調(diào)時贯涎,根據(jù)狀態(tài)旋轉(zhuǎn)箭頭以及更改文字提示。
效果如下:
到這里撼嗓,ExpandableLinearLayout的使用就介紹完畢了柬采,接下來是對源碼進行解析。
源碼解析
??ExpandableLinearLayout的原理其實很簡單且警,當使用默認的底部時粉捻,如果子條目的個數(shù)小于或者等于默認顯示的個數(shù),則不添加底部斑芜,如果子條目的個數(shù)大于默認顯示的個數(shù)肩刃,則往最后插入一個默認的底部,一開始的時候,將ExpandableLinearLayout除了默認顯示的條目和底部不隱藏以外盈包,其他的子條目都進行隱藏沸呐,當點擊“展開”的時候,將被隱藏的條目設(shè)置為顯示狀態(tài)呢燥,當點擊“收起”的時候崭添,將默認顯示條目以下的那些條目都隱藏。
首先介紹下ExpandableLinearLayout自定義的屬性:
<declare-styleable name="ExpandableLinearLayout">
<!--默認顯示的條目數(shù)-->
<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);
}
??當添加條目的時候,獲取所有子條目的個數(shù),如果是不使用默認底部的話滞谢,則只是將View添加到ExpandableLinearLayout中,當數(shù)目超過默認顯示個數(shù)時崖技,則調(diào)用hide()方法,收起除了默認顯示條目外的其他條目,即將它們設(shè)置為隱藏。如果是使用默認底部蓝角,hasBottom為是否已經(jīng)有底部的標志,如果還沒有底部則是直接往ExpandableLinearLayout中順序添加饭冬,如果已經(jīng)有底部使鹅,則是往底部前一個的位置添加View。調(diào)用的相關(guān)方法代碼如下:
/**
* 收起
*/
private void hide() {
int endIndex = useDefaultBottom ? getChildCount() - 1 : getChildCount();//如果是使用默認底部伍伤,則結(jié)束的下標是到底部之前并徘,否則則全部子條目都隱藏
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);//大于默認數(shù)目的先隱藏
}
}
/**
* 判斷是否要添加底部
* @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;
//回調(diào)
if (mListener != null){
mListener.onStateChanged(isExpand);
}
}
點擊的時候調(diào)用toggle()會根據(jù)當前狀態(tài)扰魂,進行展開或收起,如果當前是展開狀態(tài)蕴茴,即isExpand為true劝评,則調(diào)用hide()方法收起,否則倦淀,當前是收起狀態(tài)時蒋畜,調(diào)用 expand( )進行展開。這里判斷如果有設(shè)置狀態(tài)改變的監(jiān)聽撞叽,如果有則調(diào)用接口的方法將狀態(tài)傳遞出去姻成,expand( )方法的代碼如下:
/**
* 展開
*/
private void expand() {
for (int i = defaultItemCount; i < getChildCount(); i++) {
//從默認顯示條目位置以下的都顯示出來
View view = getChildAt(i);
view.setVisibility(VISIBLE);
}
}
到這里為止,ExpandableLinearLayout的源碼解析就結(jié)束了愿棋,希望可以這個控件可以幫助到大家科展。
導(dǎo)入方式####
在項目根目錄下的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