点击缩展控件的三种方式

test1

功能实现

点击伸展控件的需求还是很常见, 一般是TextView的伸缩, 因为可能要显示的文本太多, 一次性展开影响用户体能, 所以把选择权交给用户, 当然也会有性能优化方面的考虑, 这里我给出三种不同方式实现上述需求.

// 1.用Gong & Visible达到效果. 优点:简单; 缺点:浪费资源.
    public void setViewIsVisibility(View view){

        if(view == null) return;

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

第一种实现方式很简单, 在xml把控件设置为不可以, 然后在监听方法里面反转控件状态即可。

// 2.用ViewStub延迟加载布局, [与第一种方式结合使用.]
        if(viewStub.getVisibility() != View.VISIBLE){
            viewStub.setVisibility(View.VISIBLE);
            llt = ((LinearLayout) findViewById(R.id.llt));
        }else {
            setViewIsVisibility(llt);
        }

第二种实现效果与第一种相同, 不过较第一种更加节省性能, 这里用到了一个叫ViewStub的控件, 这个控件的宽高都为0,默认为不可见, 当变为可见时会把 android:layout="@layout/layout_zomm_content" 这个属性里的View加载出来.当加载出来后getVisibility()方法的返回值为0. INVISIBLE = 0x00000004; VISIBLE = 0x00000000; GONE = 0x00000008; 所以我们采用结合的方式实现伸展效果.

test2.g
// 3.增加动画效果, 思路如下: 获取控件高度, 根据控件高度做值动画改变布局高度.
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);

// 此方法会随用户的点击而调用, 所以不要用内部类的形式创建, 我这里只是节省代码量, 增加阅读性(捂脸)
  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然后获得布局的高度, 根据高度来做值动画, 但是把布局设置为GONE后控件高度为0.
因为无法实现所以只好重新测量控件高度, 测量代码如下.

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的测量有关, 因为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调用, 最外层的View宽高无疑是精确的,包裹整个屏幕的.如果屏幕是480*320那么windowSize就是这两个值中的一个. rootDimension又是什么呢?其实就是我们在xml中写的布局属性layout_widht值.

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

经历过switch语句后返回一个int类型数值,我们可以看到返回值就是MeasureSpec的三个常量与layout_xxx属性构成的.

  • MeasureSpec.EXACTLY
    表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
  • MeasureSpec.AT_MOST
    表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
  • MeasureSpec.UNSPECIFIED
    表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

上面的一系列操作其实与我们写的测量宽度操作是一致的,我们先获取宽度的属性参数, 然后把宽度与MeasureSpec.EXACTLY进行了合成,最后将数值传递给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()方法会调用onMeasure()对于该方法我们一定不佰生, 它是个抽象方法,所以我我继承View时一定要重写该方法. 而onMeasure()方法最终会调用getDefaultSize()该方法把我们的之前的数值进行了解析.

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中的数值,如果measureSpec是由MeasureSpec.EXACTLY和具体的值构成(假如是480)那么最终返回值就是480。就像我们测量的宽度一样,因为getRootMeasureSpec(desiredWindowWidth, lp.width);传入的480 而我们后面也一直用的是match_parnet所以最后显示出来的也是480即包裹整个屏幕。
等等,可你测量高度怎么传入1000最后成了包裹内容呢, 而且为什么传入0也是同样的效果?? 那我们就要看是在哪里调用了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如何测量子类的呢?它会调用下面的方法.

// 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);  
}  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容