可展開和收起的LinearLayout

ExpandableLinearLayout介紹

場景介紹

??開發(fā)的過程中,有時我們需要使用到這樣一個功能,在展示一些商品的時候,默認(rèn)只顯示前幾個,例如先顯示前三個,這樣子不會一進(jìn)入頁面就被商品列表占據(jù)了大部分,可以先讓用戶可以看到頁面的大概,當(dāng)用戶需要查看更多的商品時,點(diǎn)擊“展開”,就可以看到被隱藏的商品,點(diǎn)擊“收起”,則又回到一開始的狀態(tài),只顯示前幾個,其他的收起來了。就拿美團(tuán)外賣的訂單詳情頁的布局作為例子,請看以下圖片:

??訂單詳情頁面一開始只顯示購買的前三樣菜,當(dāng)點(diǎn)擊“點(diǎn)擊展開”時,則將購買的所有外賣都展示出來,當(dāng)點(diǎn)擊“點(diǎn)擊收起”時,則將除了前三樣菜以外的都隱藏起來。其實(shí)要完成這樣的功能并不難,為了方便自己和大家以后的開發(fā),我將其封裝成一個控件,取名為ExpandableLinearLayout,下面開始介紹它如何使用以及源碼解析。

使用方式

一、使用默認(rèn)展開和收起的底部

在布局文件中,使用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="點(diǎn)擊展開"
        app:hideText="點(diǎn)擊收起"
        ></com.chaychan.viewlib.ExpandableLinearLayout>

和LinearLayout的使用方法類似,如果是靜態(tài)數(shù)據(jù),可以在兩個標(biāo)簽中間插入子條目布局的代碼,也可以在java文件中使用代碼動態(tài)插入。useDefaultBottom是指是否使用默認(rèn)底部(默認(rèn)為true,如果需要使用默認(rèn)底部,可不寫這個屬性),如果是自定義的底部,則設(shè)置為false,下面會介紹自定義底部的用法,defaultItemCount="2",設(shè)置默認(rèn)顯示的個數(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.支持修改默認(rèn)顯示的個數(shù)

可以修改默認(rèn)顯示的個數(shù),比如將其修改為3,即defaultItemCount="3"

效果如下:

2.支持修改待展開和待收起狀態(tài)下的文字提示

可以修改待展開狀態(tài)和待收起狀態(tài)下的文字提示,比如修改expandText="查看更多",hideText="收起更多"

效果如下:

3.支持修改提示文字的大小、顏色

可以修改提示文字的大小和顏色,對應(yīng)的屬性分別是tipTextSize,tipTextColor。比如修改tipTextSize="16sp",tipTextColor="#ff7300"

效果如下:

4.支持更換箭頭的圖標(biāo)

可以修改箭頭的圖標(biāo),只需配置arrowDownImg屬性,引用對應(yīng)的圖標(biāo),這里的箭頭圖標(biāo)需要是向下的箭頭,這樣當(dāng)展開和收起時,箭頭會做相應(yīng)的旋轉(zhuǎn)動畫。設(shè)置arrowDownImg="@mipmap/arrow_down_grey",修改為灰色的向下圖標(biāo)。

效果如下:

二、使用自定義底部

布局文件中,ExpandableLinearLayout配置useDefaultBottom="false",聲明不使用默認(rèn)底部。自己定義底部的布局。

<?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)惠、實(shí)付款-->
        <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ù)的代碼,和上面演示使用默認(rèn)底部的代碼一樣
 
  //設(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("點(diǎn)擊收起");
            } else {
                tvTip.setText("點(diǎn)擊展開");
            }
        }
    });

   //為自定義的底部設(shè)置點(diǎn)擊事件
   rlBottom.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ellProduct.toggle();
        }
    });

}

  // 箭頭的動畫
  private void doArrowAnim(boolean isExpand) {
    if (isExpand) {
        // 當(dāng)前是展開,箭頭由下變?yōu)樯?        ObjectAnimator.ofFloat(ivArrow, "rotation", 0, 180).start();
    } else {
        // 當(dāng)前是收起,箭頭由上變?yōu)橄?        ObjectAnimator.ofFloat(ivArrow, "rotation", -180, 0).start();
    }
 }

主要的代碼是為ExpandableLinearLayout設(shè)置狀態(tài)改變的回調(diào),rlBottom為自定義底部的根布局RelativeLayout,為其設(shè)置點(diǎn)擊事件,當(dāng)點(diǎn)擊的時候調(diào)用ExpandableLinearLayout的toggle()方法,當(dāng)收到回調(diào)時,根據(jù)狀態(tài)旋轉(zhuǎn)箭頭以及更改文字提示。

效果如下:

到這里,ExpandableLinearLayout的使用就介紹完畢了,接下來是對源碼進(jìn)行解析。

源碼解析

??ExpandableLinearLayout的原理其實(shí)很簡單,當(dāng)使用默認(rèn)的底部時,如果子條目的個數(shù)小于或者等于默認(rèn)顯示的個數(shù),則不添加底部,如果子條目的個數(shù)大于默認(rèn)顯示的個數(shù),則往最后插入一個默認(rèn)的底部,一開始的時候,將ExpandableLinearLayout除了默認(rèn)顯示的條目和底部不隱藏以外,其他的子條目都進(jìn)行隱藏,當(dāng)點(diǎn)擊“展開”的時候,將被隱藏的條目設(shè)置為顯示狀態(tài),當(dāng)點(diǎn)擊“收起”的時候,將默認(rèn)顯示條目以下的那些條目都隱藏。

首先介紹下ExpandableLinearLayout自定義的屬性:

<declare-styleable name="ExpandableLinearLayout">
    <!--默認(rèn)顯示的條目數(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" />
    <!--向下的箭頭的圖標(biāo)-->
    <attr name="arrowDownImg" format="reference" />
    <!--是否使用默認(rèn)的底部-->
    <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);
}

 /**
 * 渲染完成時初始化默認(rèn)底部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){
        //如果不使用默認(rèn)底部
        addView(view);
        if (childCount > defaultItemCount){
            hide();
        }
        return;
    }

    //使用默認(rèn)底部
    if (!hasBottom) {
        //如果還沒有底部
        addView(view);
    } else {
        addView(view, childCount - 2);//插在底部之前
    }
    refreshUI(view);
}

??當(dāng)添加條目的時候,獲取所有子條目的個數(shù),如果是不使用默認(rèn)底部的話,則只是將View添加到ExpandableLinearLayout中,當(dāng)數(shù)目超過默認(rèn)顯示個數(shù)時,則調(diào)用hide()方法,收起除了默認(rèn)顯示條目外的其他條目,即將它們設(shè)置為隱藏。如果是使用默認(rèn)底部,hasBottom為是否已經(jīng)有底部的標(biāo)志,如果還沒有底部則是直接往ExpandableLinearLayout中順序添加,如果已經(jīng)有底部,則是往底部前一個的位置添加View。調(diào)用的相關(guān)方法代碼如下:

 /**
 * 收起
 */
private void hide() {
    int endIndex = useDefaultBottom ? getChildCount() - 1 : getChildCount();//如果是使用默認(rèn)底部,則結(jié)束的下標(biāo)是到底部之前,否則則全部子條目都隱藏
    for (int i = defaultItemCount; i < endIndex; i++) {
        //從默認(rèn)顯示條目位置以下的都隱藏
        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) {
            //剛超過默認(rèn),判斷是否要添加底部
            justToAddBottom(childCount);
        }
        view.setVisibility(GONE);//大于默認(rèn)數(shù)目的先隱藏
    }
}

/**
 * 判斷是否要添加底部
 * @param childCount
 */
private void justToAddBottom(int childCount) {
    if (childCount > defaultItemCount) {
        if (useDefaultBottom && !hasBottom) {
            //要使用默認(rèn)底部,并且還沒有底部
            addView(bottomView);//添加底部
            hide();
            hasBottom = true;
        }
    }
}

默認(rèn)底部的點(diǎn)擊事件:

 @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ǎn)擊的時候調(diào)用toggle()會根據(jù)當(dāng)前狀態(tài),進(jìn)行展開或收起,如果當(dāng)前是展開狀態(tài),即isExpand為true,則調(diào)用hide()方法收起,否則,當(dāng)前是收起狀態(tài)時,調(diào)用 expand( )進(jìn)行展開。這里判斷如果有設(shè)置狀態(tài)改變的監(jiān)聽,如果有則調(diào)用接口的方法將狀態(tài)傳遞出去,expand( )方法的代碼如下:

/**
 * 展開
 */
private void expand() {
    for (int i = defaultItemCount; i < getChildCount(); i++) {
        //從默認(rèn)顯示條目位置以下的都顯示出來
        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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,991評論 2 59
  • 95/96年的一個晚上,一個父親騎車帶著上幼兒園的女兒回家。 之后發(fā)生了車禍,更重要的是司機(jī)肇事逃逸。讓他們本可以...
    又見顧唐路閱讀 694評論 9 14
  • 先來說說網(wǎng)站SEO長尾關(guān)鍵詞有哪些優(yōu)勢?流量較小,數(shù)量多,甚至大多數(shù)詞還沒有被挖掘出來。占有全網(wǎng)站流量比例...
    fatgk441閱讀 778評論 0 1
  • 豬場母豬產(chǎn)死胎現(xiàn)象常有發(fā)生:有疾病造成的、高溫時造成的、死胎一般包括母豬分娩時發(fā)生死亡的正常胎兒(白胎)和妊娠前、...
    5bbdb32c24aa閱讀 1,503評論 0 2

友情鏈接更多精彩內(nèi)容