點擊縮展控件的三種方式

test1

功能實現(xiàn)

點擊伸展控件的需求還是很常見, 一般是TextView的伸縮, 因為可能要顯示的文本太多, 一次性展開影響用戶體能, 所以把選擇權(quán)交給用戶, 當然也會有性能優(yōu)化方面的考慮, 這里我給出三種不同方式實現(xiàn)上述需求.

// 1.用Gong & Visible達到效果. 優(yōu)點:簡單; 缺點:浪費資源.
    public void setViewIsVisibility(View view){

        if(view == null) return;

        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        } else{
            view.setVisibility(View.GONE);
        }
    }

第一種實現(xiàn)方式很簡單, 在xml把控件設置為不可以, 然后在監(jiān)聽方法里面反轉(zhuǎn)控件狀態(tài)即可。

// 2.用ViewStub延遲加載布局, [與第一種方式結(jié)合使用.]
        if(viewStub.getVisibility() != View.VISIBLE){
            viewStub.setVisibility(View.VISIBLE);
            llt = ((LinearLayout) findViewById(R.id.llt));
        }else {
            setViewIsVisibility(llt);
        }

第二種實現(xiàn)效果與第一種相同, 不過較第一種更加節(jié)省性能, 這里用到了一個叫ViewStub的控件, 這個控件的寬高都為0,默認為不可見, 當變?yōu)榭梢姇r會把 android:layout="@layout/layout_zomm_content" 這個屬性里的View加載出來.當加載出來后getVisibility()方法的返回值為0. INVISIBLE = 0x00000004; VISIBLE = 0x00000000; GONE = 0x00000008; 所以我們采用結(jié)合的方式實現(xiàn)伸展效果.

test2.g
// 3.增加動畫效果, 思路如下: 獲取控件高度, 根據(jù)控件高度做值動畫改變布局高度.
if(mHeight < 0){
  mHeight= getViewHeight(); // 因為我們在xml寫的高度為0, 所以要重新測量.
}
----------------------------------------------------------------------
private void executeAnimation() {
  if(mHeight < 0)  return;     
  if(llt.getHeight() != 0){
    p = mHeight;
    s = 0;
   }else {
    s = mHeight;
    p = 0;
  }

  final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) llt.getLayoutParams();
  ValueAnimator animator = ValueAnimator.ofInt(p,s);

// 此方法會隨用戶的點擊而調(diào)用, 所以不要用內(nèi)部類的形式創(chuàng)建, 我這里只是節(jié)省代碼量, 增加閱讀性(捂臉)
  animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   @Override
    public void onAnimationUpdate(ValueAnimator animation) {

      int animatedValue = (int) animation.getAnimatedValue();
      lp.height = animatedValue;
      llt.setLayoutParams(lp);                
     }
  });

  animator.setDuration(500);
  animator.start();
}

第三種方式本來是想在xml中把布局設置為GONE然后獲得布局的高度, 根據(jù)高度來做值動畫, 但是把布局設置為GONE后控件高度為0.
因為無法實現(xiàn)所以只好重新測量控件高度, 測量代碼如下.

public int getViewHeight(){
  llt.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
  int width = llt.getLayoutParams().width;
  int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.AT_MOST);
  int widthMakeMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
  llt.measure(widthMakeMeasureSpec, heightMeasureSpec);
  mHeight = llt.getMeasuredHeight();
}

其實這樣寫measure(0, 0) , 也能得到控件的高度, 但我想做為一名有追求的程序員還是搞清楚原因比較好.

View的測量

因為在布局文件中將控件的高度設置成0dp,所以我們首先要做的就是更改0dp, 原因也與view的測量有關(guān), 因為view寬高受各方面影響當把View設置成match_parent時View的寬高主要受父控件影響, 設置為wrap_content它又受子View的影響, 只有當把View的寬寫死時它才能自己當家做主, 基于VIew寬高的復雜性設計者把View的最終進行了雙重判斷, 代碼如下 :

private int getRootMeasureSpec(int windowSize, int rootDimension) {  
    int measureSpec;  
    switch (rootDimension) {  
    case ViewGroup.LayoutParams.MATCH_PARENT:  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
        break;  
    case ViewGroup.LayoutParams.WRAP_CONTENT:  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
        break;  
    default:  
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
        break;  
    }  
    return measureSpec;  
}  

這個方法是由最外層的FrameLayout調(diào)用, 最外層的View寬高無疑是精確的,包裹整個屏幕的.如果屏幕是480*320那么windowSize就是這兩個值中的一個. rootDimension又是什么呢?其實就是我們在xml中寫的布局屬性layout_widht值.

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  

經(jīng)歷過switch語句后返回一個int類型數(shù)值,我們可以看到返回值就是MeasureSpec的三個常量與layout_xxx屬性構(gòu)成的.

  • MeasureSpec.EXACTLY
    表示父視圖希望子視圖的大小應該是由specSize的值來決定的,系統(tǒng)默認會按照這個規(guī)則來設置子視圖的大小,開發(fā)人員當然也可以按照自己的意愿設置成任意的大小。
  • MeasureSpec.AT_MOST
    表示子視圖最多只能是specSize中指定的大小,開發(fā)人員應該盡可能小得去設置這個視圖,并且保證不會超過specSize。系統(tǒng)默認會按照這個規(guī)則來設置子視圖的大小,開發(fā)人員當然也可以按照自己的意愿設置成任意的大小。
  • MeasureSpec.UNSPECIFIED
    表示開發(fā)人員可以將視圖按照自己的意愿設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。

上面的一系列操作其實與我們寫的測量寬度操作是一致的,我們先獲取寬度的屬性參數(shù), 然后把寬度與MeasureSpec.EXACTLY進行了合成,最后將數(shù)值傳遞給measure方法.

  llt.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
  int width = llt.getLayoutParams().width;
  int widthMakeMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
  llt.measure(widthMakeMeasureSpec, heightMeasureSpec);

measure()方法會調(diào)用onMeasure()對于該方法我們一定不佰生, 它是個抽象方法,所以我我繼承View時一定要重寫該方法. 而onMeasure()方法最終會調(diào)用getDefaultSize()該方法把我們的之前的數(shù)值進行了解析.

public static int getDefaultSize(int size, int measureSpec) {  
    int result = size;  
    int specMode = MeasureSpec.getMode(measureSpec);  
    int specSize = MeasureSpec.getSize(measureSpec);  
    switch (specMode) {  
    case MeasureSpec.UNSPECIFIED:  
        result = size;  
        break;  
    case MeasureSpec.AT_MOST:  
    case MeasureSpec.EXACTLY:  
        result = specSize;  
        break;  
    }  
    return result;  
}  

AT_MOST與EXACTLY最終返回值就是measureSpec中的數(shù)值,如果measureSpec是由MeasureSpec.EXACTLY和具體的值構(gòu)成(假如是480)那么最終返回值就是480。就像我們測量的寬度一樣,因為getRootMeasureSpec(desiredWindowWidth, lp.width);傳入的480 而我們后面也一直用的是match_parnet所以最后顯示出來的也是480即包裹整個屏幕。
等等,可你測量高度怎么傳入1000最后成了包裹內(nèi)容呢, 而且為什么傳入0也是同樣的效果?? 那我們就要看是在哪里調(diào)用了getDefaultSize()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
                       widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
                       heightMeasureSpec));
    }
--------------------------------------------------------------
protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

還是什么都沒有啊!!! 原因是我們這個時候要看的不是View的onMeasure方法而具體子類的onMeasure方法, 我們在自定View時也是在onMeasure方法對自己的view進行測量的,那傳入的0是什么意思?我們看0對應的是什么模式

public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY     = 1 << MODE_SHIFT;   
public static final int AT_MOST     = 2 << MODE_SHIFT;

可以看到傳入0對應的是UNSPECIFIED模式, 那子類在獲取模式時就會走UNSPECIFIED的判斷語句里.
ViewGroup如何測量子類的呢?它會調(diào)用下面的方法.

// widthMeasureSpec 與 heightMeasureSpec 就是父View自己的組合值.
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
    final int size = mChildrenCount;  
    final View[] children = mChildren;  
    for (int i = 0; i < size; ++i) {  
        final View child = children[i];  
        // 這是解釋了為什么把View設置為GONEView不占位,也無法獲得高度.
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {  
            // 兒子看爸爸臉色, 兒子的寬度受父親影響.
            measureChild(child, widthMeasureSpec, heightMeasureSpec);  
        }  
    }  
} 
-------------------------------------------------------------------------
protected void measureChild(View child, int parentWidthMeasureSpec,  
        int parentHeightMeasureSpec) {  
    final LayoutParams lp = child.getLayoutParams();  
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
            mPaddingLeft + mPaddingRight, lp.width);  
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
            mPaddingTop + mPaddingBottom, lp.height);  
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
}  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

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