android成神之路Android开发Android知识

点击缩展控件的三种方式

2017-02-15  本文已影响172人  Il_mondo
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.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);  
}  
上一篇下一篇

猜你喜欢

热点阅读