可展開和收起的LinearLayout

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坞琴,一起剝皮案震驚了整個濱河市哨查,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剧辐,老刑警劉巖解恰,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浙于,居然都是意外死亡护盈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門羞酗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腐宋,“玉大人,你說我怎么就攤上這事檀轨⌒鼐海” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵参萄,是天一觀的道長卫枝。 經(jīng)常有香客問我,道長讹挎,這世上最難降的妖魔是什么校赤? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮筒溃,結(jié)果婚禮上马篮,老公的妹妹穿的比我還像新娘。我一直安慰自己怜奖,他們只是感情好浑测,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歪玲,像睡著了一般迁央。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滥崩,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天岖圈,我揣著相機與錄音,去河邊找鬼夭委。 笑死幅狮,一個胖子當著我的面吹牛募强,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播崇摄,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼擎值,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逐抑?” 一聲冷哼從身側(cè)響起鸠儿,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厕氨,沒想到半個月后进每,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡命斧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年田晚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片国葬。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡贤徒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汇四,到底是詐尸還是另有隱情接奈,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布通孽,位于F島的核電站序宦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏背苦。R本人自食惡果不足惜互捌,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糠惫。 院中可真熱鬧疫剃,春花似錦、人聲如沸硼讽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽固阁。三九已至,卻和暖如春城菊,著一層夾襖步出監(jiān)牢的瞬間备燃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工凌唬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留并齐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像况褪,于是被迫代替她去往敵國和親撕贞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

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