首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义View常用例子二(点击展开隐藏控件,九宫格图片控件)

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

作者头像
程序员徐公
修改2019-08-07 17:46:06
1.1K0
修改2019-08-07 17:46:06
举报

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1341969

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

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

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

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

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

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

图二源码下载地址

图一源码下载地址:

图一源码

public class CollapseView extends LinearLayout {
    private long duration = 350;
    private Context mContext;
    private TextView mNumberTextView;
    private TextView mTitleTextView;
    private RelativeLayout mContentRelativeLayout;
    private RelativeLayout mTitleRelativeLayout;
    private ImageView mArrowImageView;
    int parentWidthMeasureSpec;
    int parentHeightMeasureSpec;
    private String TAG = "xujun";

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

    public CollapseView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        LayoutInflater.from(mContext).inflate(R.layout.collapse_layout, this);
        initView();
    }


    private void initView() {
        mNumberTextView = (TextView) findViewById(R.id.numberTextView);
        mTitleTextView = (TextView) findViewById(R.id.titleTextView);
        mTitleRelativeLayout = (RelativeLayout) findViewById(R.id.titleRelativeLayout);
        mContentRelativeLayout = (RelativeLayout) findViewById(R.id.contentRelativeLayout);
        mArrowImageView = (ImageView) findViewById(R.id.arrowImageView);
        mTitleRelativeLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                rotateArrow();
            }
        });
        mNumberTextView.setBackgroundResource(R.drawable.circle);
        Drawable circleShape = createCircleShape(Color.BLACK);
        mNumberTextView.setBackgroundDrawable(circleShape);
        collapse(mContentRelativeLayout);
    }

    public void setNumber(String number) {
        if (!TextUtils.isEmpty(number)) {
            mNumberTextView.setText(number);
        }
    }

    public void setTitle(String title) {
        if (!TextUtils.isEmpty(title)) {
            mTitleTextView.setText(title);
        }
    }

    public void setContent(int resID) {
        View view = LayoutInflater.from(mContext).inflate(resID, null);
        RelativeLayout.LayoutParams layoutParams =
                new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, RelativeLayout
                        .LayoutParams.WRAP_CONTENT);
        view.setLayoutParams(layoutParams);
        mContentRelativeLayout.addView(view);
    }

    /**
     * 若使用这个方法,强制layoutParams must be RelativeLayout.LayoutParams,防止在某些情况下出现错误
     *
     * @param view
     */
    public void setContent(View view) {
        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();

        if (layoutParams == null) {
            layoutParams = new RelativeLayout.LayoutParams(
                    LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        }

        if (!(layoutParams instanceof RelativeLayout.LayoutParams)) {
            throw new IllegalStateException("layoutParams must be RelativeLayout.LayoutParams ");
        }

        view.setLayoutParams(layoutParams);
        mContentRelativeLayout.addView(view);
    }

    public void rotateArrow() {
        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);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        parentWidthMeasureSpec = widthMeasureSpec;
        parentHeightMeasureSpec = heightMeasureSpec;
    }

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




    // 展开
    private void expand(final View view) {

        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();

    }

    private int getMode(int parentMeasureSpec) {

        if (parentMeasureSpec == MeasureSpec.EXACTLY) {
            return MeasureSpec.AT_MOST;
        } else if (parentMeasureSpec == MeasureSpec.AT_MOST) {
            return MeasureSpec.AT_MOST;
        } else {
            return parentMeasureSpec;
        }
    }

    private void setWidth(View view, int width) {
        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        layoutParams.width = width;
        view.setLayoutParams(layoutParams);
        view.requestLayout();
    }

    // 折叠
    private void collapse(final View view) {
        final int measuredHeight = view.getMeasuredHeight();
        final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.setDuration(duration);
        final int viewMeasuredWidth = view.getMeasuredWidth();

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                float precent = animation.getAnimatedFraction();
                Log.i(TAG, "onAnimationUpdate: precent" + precent);
                int width = (int) (viewMeasuredWidth - viewMeasuredWidth * precent);
                setWidth(view, width);
//                动画执行结束的时候,设置View为View.GONE,同时移除监听器
                if (precent == 1) {
                    view.setVisibility(View.GONE);
                    valueAnimator.removeAllUpdateListeners();
                }
            }
        });
        valueAnimator.start();
    }
}

思路解析

  1. 如图所示,图一一四个部分组成,数字,标题,箭头,图片,点击标题所在的那一行,图片回相应地隐藏或者显示。
  2. 数字,标题,箭头都在同一个相对布局里面,图片在单独的一个相对布局中,总体由 LinearLayout构成,布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="#ffffff"
              android:orientation="vertical">
    <RelativeLayout
        android:id="@+id/titleRelativeLayout"
        android:padding="30px"
        android:layout_width="match_parent"
        android:layout_height="170px"
        android:clickable="true">

        <TextView
            android:id="@+id/numberTextView"
            android:layout_width="70px"
            android:layout_height="70px"
            android:gravity="center"
            android:layout_centerVertical="true"

            android:clickable="false"
            android:text="1"
            android:textStyle="bold"
            android:textColor="#EBEFEC"
            android:textSize="35px" />

        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toRightOf="@id/numberTextView"
            android:layout_marginLeft="30px"
            android:clickable="false"
            android:textColor="#1d953f"
            android:textSize="46px" />


        <ImageView
            android:id="@+id/arrowImageView"
            android:layout_width="48px"
            android:layout_height="27px"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:background="@mipmap/arrow_down"
            android:clickable="false"
            android:scaleType="fitCenter" />
    </RelativeLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="2px"
        android:layout_below="@id/titleRelativeLayout"
        android:background="#E7E7EF"
        android:clickable="false"
        />

    <RelativeLayout
        android:id="@+id/contentRelativeLayout"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </RelativeLayout>

</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()方法,基本所有的逻辑都放在这里

  • 首先我们先根据孩子的数量确定有多少行多少列
  • 接着判断是否需要复用 ,不需要复用的话,直接添加孩子,需要复用的话,判断需要新设置的孩子的数量是否大于缓存的孩子的数量,小于的话,继续添加孩子,知道孩子的数量达到我们需要的孩子的数量为止,小于的话,移除多余的缓存的孩子的数量
  • 接着再摆放每个孩子的位置,并设置它的Url

注意我们这里之所以需要判断是否需要复用,是因为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;
    }


}

图二源码下载地址

图一源码下载地址:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年06月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 自定义View常用例子二(点击展开隐藏控件,九宫格图片控件)
    • 一.废话不多说了,先来看一下效果图
      • 图一源码
        • 思路解析
          • 图一的源码分析到此为止,源码下载地址:https://github.com/gdutxiaoxu/CustomViewDemo2.git
        • 图二源码分析
          • 1.思路解析,
          • 注意我们这里之所以需要判断是否需要复用,是因为ListView或RecyclerView的缓存机制,若每次setImagesData()的时候直接添加,会导致添加多个孩子,若直接移除所有孩子的话,性能相对来说较差,所以我们进行缓存,到此源码分析位置。
          • 代码如下
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档