[关闭]
@xujun94 2016-07-17T16:13:12.000000Z 字数 12283 阅读 1375

自定义View常用例子二(点击展开隐藏控件,九宫格图片控件)

今天博客的主要内容是两个常见的自定义控件,第一个是我们经常看到的点击隐藏点击查看控件,第二个控件是仿微信朋友圈的九宫格图片控件,相对上一篇的流布式布局来说,这篇博客更容易,只不过涉及更多的知识点而已

转载请注明原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/51772308

一.废话不多说了,先来看一下效果图

  1. 图一效果,点击隐藏,展开


  1. 图二效果,类似于朋友圈九宫格图片


图二源码下载地址

图一源码下载地址:

图一源码

  1. public class CollapseView extends LinearLayout {
  2. private long duration = 350;
  3. private Context mContext;
  4. private TextView mNumberTextView;
  5. private TextView mTitleTextView;
  6. private RelativeLayout mContentRelativeLayout;
  7. private RelativeLayout mTitleRelativeLayout;
  8. private ImageView mArrowImageView;
  9. int parentWidthMeasureSpec;
  10. int parentHeightMeasureSpec;
  11. private String TAG = "xujun";
  12. public CollapseView(Context context) {
  13. this(context, null);
  14. }
  15. public CollapseView(Context context, AttributeSet attrs) {
  16. super(context, attrs);
  17. mContext = context;
  18. LayoutInflater.from(mContext).inflate(R.layout.collapse_layout, this);
  19. initView();
  20. }
  21. private void initView() {
  22. mNumberTextView = (TextView) findViewById(R.id.numberTextView);
  23. mTitleTextView = (TextView) findViewById(R.id.titleTextView);
  24. mTitleRelativeLayout = (RelativeLayout) findViewById(R.id.titleRelativeLayout);
  25. mContentRelativeLayout = (RelativeLayout) findViewById(R.id.contentRelativeLayout);
  26. mArrowImageView = (ImageView) findViewById(R.id.arrowImageView);
  27. mTitleRelativeLayout.setOnClickListener(new OnClickListener() {
  28. @Override
  29. public void onClick(View v) {
  30. rotateArrow();
  31. }
  32. });
  33. mNumberTextView.setBackgroundResource(R.drawable.circle);
  34. Drawable circleShape = createCircleShape(Color.BLACK);
  35. mNumberTextView.setBackgroundDrawable(circleShape);
  36. collapse(mContentRelativeLayout);
  37. }
  38. public void setNumber(String number) {
  39. if (!TextUtils.isEmpty(number)) {
  40. mNumberTextView.setText(number);
  41. }
  42. }
  43. public void setTitle(String title) {
  44. if (!TextUtils.isEmpty(title)) {
  45. mTitleTextView.setText(title);
  46. }
  47. }
  48. public void setContent(int resID) {
  49. View view = LayoutInflater.from(mContext).inflate(resID, null);
  50. RelativeLayout.LayoutParams layoutParams =
  51. new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, RelativeLayout
  52. .LayoutParams.WRAP_CONTENT);
  53. view.setLayoutParams(layoutParams);
  54. mContentRelativeLayout.addView(view);
  55. }
  56. /**
  57. * 若使用这个方法,强制layoutParams must be RelativeLayout.LayoutParams,防止在某些情况下出现错误
  58. *
  59. * @param view
  60. */
  61. public void setContent(View view) {
  62. ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
  63. if (layoutParams == null) {
  64. layoutParams = new RelativeLayout.LayoutParams(
  65. LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
  66. }
  67. if (!(layoutParams instanceof RelativeLayout.LayoutParams)) {
  68. throw new IllegalStateException("layoutParams must be RelativeLayout.LayoutParams ");
  69. }
  70. view.setLayoutParams(layoutParams);
  71. mContentRelativeLayout.addView(view);
  72. }
  73. public void rotateArrow() {
  74. int degree = 0;
  75. if (mArrowImageView.getTag() == null || mArrowImageView.getTag().equals(true)) {
  76. mArrowImageView.setTag(false);
  77. degree = -180;
  78. expand(mContentRelativeLayout);
  79. } else {
  80. degree = 0;
  81. mArrowImageView.setTag(true);
  82. collapse(mContentRelativeLayout);
  83. }
  84. mArrowImageView.animate().setDuration(duration).rotation(degree);
  85. }
  86. @Override
  87. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  88. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  89. parentWidthMeasureSpec = widthMeasureSpec;
  90. parentHeightMeasureSpec = heightMeasureSpec;
  91. }
  92. @Override
  93. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  94. super.onLayout(changed, l, t, r, b);
  95. }
  96. // 展开
  97. private void expand(final View view) {
  98. view.setVisibility(View.VISIBLE);
  99. int childWidthMode = getMode(parentWidthMeasureSpec);
  100. int childHeightMode = getMode(parentHeightMeasureSpec);
  101. view.measure(childWidthMode, childHeightMode);
  102. final int measuredWidth = view.getMeasuredWidth();
  103. final int measuredHeight = view.getMeasuredHeight();
  104. final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
  105. valueAnimator.setDuration(duration);
  106. valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  107. @Override
  108. public void onAnimationUpdate(ValueAnimator animation) {
  109. float precent = animation.getAnimatedFraction();
  110. int width = (int) (measuredWidth * precent);
  111. setWidth(view, width);
  112. if (precent == 1) {
  113. valueAnimator.removeAllUpdateListeners();
  114. }
  115. }
  116. });
  117. valueAnimator.start();
  118. }
  119. private int getMode(int parentMeasureSpec) {
  120. if (parentMeasureSpec == MeasureSpec.EXACTLY) {
  121. return MeasureSpec.AT_MOST;
  122. } else if (parentMeasureSpec == MeasureSpec.AT_MOST) {
  123. return MeasureSpec.AT_MOST;
  124. } else {
  125. return parentMeasureSpec;
  126. }
  127. }
  128. private void setWidth(View view, int width) {
  129. ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
  130. layoutParams.width = width;
  131. view.setLayoutParams(layoutParams);
  132. view.requestLayout();
  133. }
  134. // 折叠
  135. private void collapse(final View view) {
  136. final int measuredHeight = view.getMeasuredHeight();
  137. final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
  138. valueAnimator.setDuration(duration);
  139. final int viewMeasuredWidth = view.getMeasuredWidth();
  140. valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  141. @Override
  142. public void onAnimationUpdate(ValueAnimator animation) {
  143. float precent = animation.getAnimatedFraction();
  144. Log.i(TAG, "onAnimationUpdate: precent" + precent);
  145. int width = (int) (viewMeasuredWidth - viewMeasuredWidth * precent);
  146. setWidth(view, width);
  147. // 动画执行结束的时候,设置View为View.GONE,同时移除监听器
  148. if (precent == 1) {
  149. view.setVisibility(View.GONE);
  150. valueAnimator.removeAllUpdateListeners();
  151. }
  152. }
  153. });
  154. valueAnimator.start();
  155. }
  156. }

思路解析

  1. 如图所示,图一一四个部分组成,数字,标题,箭头,图片,点击标题所在的那一行,图片回相应地隐藏或者显示。
  2. 数字,标题,箭头都在同一个相对布局里面,图片在单独的一个相对布局中,总体由 LinearLayout构成,布局文件如下
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="wrap_content"
  5. android:background="#ffffff"
  6. android:orientation="vertical">
  7. <RelativeLayout
  8. android:id="@+id/titleRelativeLayout"
  9. android:padding="30px"
  10. android:layout_width="match_parent"
  11. android:layout_height="170px"
  12. android:clickable="true">
  13. <TextView
  14. android:id="@+id/numberTextView"
  15. android:layout_width="70px"
  16. android:layout_height="70px"
  17. android:gravity="center"
  18. android:layout_centerVertical="true"
  19. android:clickable="false"
  20. android:text="1"
  21. android:textStyle="bold"
  22. android:textColor="#EBEFEC"
  23. android:textSize="35px" />
  24. <TextView
  25. android:id="@+id/titleTextView"
  26. android:layout_width="match_parent"
  27. android:layout_height="wrap_content"
  28. android:layout_centerVertical="true"
  29. android:layout_toRightOf="@id/numberTextView"
  30. android:layout_marginLeft="30px"
  31. android:clickable="false"
  32. android:textColor="#1d953f"
  33. android:textSize="46px" />
  34. <ImageView
  35. android:id="@+id/arrowImageView"
  36. android:layout_width="48px"
  37. android:layout_height="27px"
  38. android:layout_alignParentRight="true"
  39. android:layout_centerVertical="true"
  40. android:background="@mipmap/arrow_down"
  41. android:clickable="false"
  42. android:scaleType="fitCenter" />
  43. </RelativeLayout>
  44. <View
  45. android:layout_width="match_parent"
  46. android:layout_height="2px"
  47. android:layout_below="@id/titleRelativeLayout"
  48. android:background="#E7E7EF"
  49. android:clickable="false"
  50. />
  51. <RelativeLayout
  52. android:id="@+id/contentRelativeLayout"
  53. android:visibility="gone"
  54. android:layout_width="match_parent"
  55. android:layout_height="wrap_content">
  56. </RelativeLayout>
  57. </LinearLayout>

把contentRelativeLayout的visibility设置为android:visibility="gone",是因为一开始不用加载这个相对局部,这样它不会占用位置

  1. 在代码中初始化布局,并给 mTitleRelativeLayout设置点击事件。

 mTitleRelativeLayout.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    rotateArrow();
                }
            });

我们来看 rotateArrow()里面我们做了什么,其实就是根据相应的动画执行箭头旋转的动作和更改
contentRelativeLayout的高度
核心代码如下:


  int degree = 0;
  if (mArrowImageView.getTag() == null || mArrowImageView.getTag().equals(true)) {
        mArrowImageView.setTag(false);
        degree = -180;
        expand(mContentRelativeLayout);
   } else {
        degree = 0;
        mArrowImageView.setTag(true);
        collapse(mContentRelativeLayout);
   }
   mArrowImageView.animate().setDuration(duration).rotation(degree);

我们在来看一下在expand我们做了什么,其实就是给contentRelativeLayout执行一个动画,在动画的执行过程中不断改变contentRelativeLayout的高度,注意在执行动画之前,我们需要小调用view.measure(childWidthMode, childHeightMode);方法,这样我们可能获取到高度


    view.setVisibility(View.VISIBLE);
    int childWidthMode = getMode(parentWidthMeasureSpec);
    int childHeightMode = getMode(parentHeightMeasureSpec);
    view.measure(childWidthMode, childHeightMode);
    final int measuredWidth = view.getMeasuredWidth();
    final int measuredHeight = view.getMeasuredHeight();

    final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
    valueAnimator.setDuration(duration);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float precent = animation.getAnimatedFraction();
            int width = (int) (measuredWidth * precent);
            setWidth(view, width);
            // 动画执行结束的时候,同时移除监听器
            if (precent == 1) {
                valueAnimator.removeAllUpdateListeners();
            }
        }
    });
    valueAnimator.start();     

反之,隐藏也是执行一个动画,不断改变高度,只不过高度 是越来越小,直至为0为止

图一的源码分析到此为止,源码下载地址:https://github.com/gdutxiaoxu/CustomViewDemo2.git


图二源码分析

源码下载地址

1.思路解析,

1)首先我们自己自定义一个CustomImageView,在这个类里面我们给其提供了一个方法

public void setImageUrl(String url);

在这个方法里面其实我们做的工作就是Picasso框架加载图片,即图片交给CustomImageView自己去加载,更符合面向对象的四位

    if (!TextUtils.isEmpty(url)) {
        this.url = url;
        if (isAttachedToWindow) {
            Picasso.with(getContext()).load(url).placeholder(new ColorDrawable(Color.parseColor("#f5f5f5"))).into(this);
        }
    }

2)接着我们自定义一个NineGridlayout,继承ViewGroup,在这个类里面我们主要做的工作就是添加孩子,并确定每个孩子的位置

首先我们在构造方法里面初始化我们控件需要的宽度

public NineGridlayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    ScreenTools screenTools=ScreenTools.instance(getContext());
  //        初始总宽度
    totalWidth=screenTools.getScreenWidth()-screenTools.dip2px(80);
}

接着我们提供了setImagesData()方法,基本所有的逻辑都放在这里

注意我们这里之所以需要判断是否需要复用,是因为ListView或RecyclerView的缓存机制,若每次setImagesData()的时候直接添加,会导致添加多个孩子,若直接移除所有孩子的话,性能相对来说较差,所以我们进行缓存,到此源码分析位置。

代码如下

/**
 * 博客地址:http://blog.csdn.net/gdutxiaoxu
 * @author xujun
 * @time 2015/11/27 16:13.
 */
public class NineGridlayout extends ViewGroup {

    /**
     * 图片之间的间隔
     */
    private int gap = 5;
    private int columns;//列数
    private int rows;//行数
    private List listData;
    private int totalWidth;

    private final int MAX_COLUMNS=3;
    private final int MAX_ROW3=3;

    public NineGridlayout(Context context) {
        this(context,null);
    }

    public NineGridlayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        ScreenTools screenTools=ScreenTools.instance(getContext());
//        初始总宽度
        totalWidth=screenTools.getScreenWidth()-screenTools.dip2px(80);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }
    private void layoutChildrenView(){
        int childrenCount = listData.size();

        int singleWidth = (totalWidth - gap * (3 - 1)) / 3;
        int singleHeight = singleWidth;


        /**
         * 根据子view数量确定高度,这里直接调用setLayoutParams设置NineGridlayout的高度
         * 
         */

        LayoutParams params = getLayoutParams();
        int marginHeight = getPaddingTop() + getPaddingTop();
        params.height = singleHeight * rows + gap * (rows - 1)+marginHeight;
        setLayoutParams(params);

//摆放孩子的位置
        for (int i = 0; i < childrenCount; i++) {
            CustomImageView childrenView = (CustomImageView) getChildAt(i);
            childrenView.setImageUrl(((Image) listData.get(i)).getUrl());
            int[] position = findPosition(i);

//            加上getPaddingLeft(),为了支持Padding属性
            int left = (singleWidth + gap) * position[1]+getPaddingLeft();
            int top = (singleHeight + gap) * position[0]+getPaddingTop();
            int right = left + singleWidth;
            int bottom = top + singleHeight;

            childrenView.layout(left, top, right, bottom);
        }

    }


    private int[] findPosition(int childNum) {
        int[] position = new int[2];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if ((i * columns + j) == childNum) {
                    position[0] = i;//行
                    position[1] = j;//列
                    break;
                }
            }
        }
        return position;
    }

    public int getGap() {
        return gap;
    }

    public void setGap(int gap) {
        this.gap = gap;
    }


    public void setImagesData(List<Image> lists) {
        if (lists == null || lists.isEmpty()) {
            return;
        }
        //初始化布局
        generateChildrenLayout(lists.size());
        //这里做一个重用view的处理
        if (listData == null) {
            int i = 0;
            while (i < lists.size()) {
                CustomImageView iv = generateImageView();
                addView(iv,generateDefaultLayoutParams());
                i++;
            }
        } else {
            int oldViewCount = listData.size();
            int newViewCount = lists.size();
            if (oldViewCount > newViewCount) {
                removeViews(newViewCount - 1, oldViewCount - newViewCount);
            } else if (oldViewCount < newViewCount) {
                for (int i = 0; i < newViewCount - oldViewCount; i++) {
                    CustomImageView iv = generateImageView();
                    addView(iv,generateDefaultLayoutParams());
                }
            }
        }
        listData = lists;
        layoutChildrenView();
    }


    /**
     * 根据图片个数确定行列数量
     * 对应关系如下
     * num  row column
     * 1       1    1
     * 2       1    2
     * 3       1    3
     * 4       2    2
     * 5       2    3
     * 6       2    3
     * 7       3    3
     * 8       3    3
     * 9       3    3
     *
     * @param length
     */
    private void generateChildrenLayout(int length) {
        if (length <= MAX_COLUMNS) {
            rows = 1;
            columns = length;
        } else if (length <= MAX_COLUMNS*2) {
            rows = 2;
            columns = MAX_COLUMNS;
            if (length == MAX_COLUMNS+1) {
                columns = 2;
            }
        } else {
            rows = 3;
            columns = MAX_COLUMNS;
        }
    }

    private CustomImageView generateImageView() {
        CustomImageView iv = new CustomImageView(getContext());
        iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
        iv.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
        iv.setBackgroundColor(Color.parseColor("#f5f5f5"));
        return iv;
    }


}

图二源码下载地址

图一源码下载地址:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注