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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/51772308

自定义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",是因为一开始不用加载这个相对局部,这样它不会占用位置

3. 在代码中初始化布局,并给 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;
    }


}

图二源码下载地址

图一源码下载地址:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

仿淘宝购买详情页购买缩小动画

偶尔一个时候,我们产品的详情页面也做的和淘宝神识,为了加强 的体验,我们加了一些动画,下面说说淘宝详情的缩放详情页的动画怎么做的吧。 先上两张图, ? ? 其实...

2118
来自专栏小巫技术博客

A020-列表容器之ListView

前面介绍了Android UI中的五大布局容器,本节课介绍实际项目当中经常会用到的组件-ListView,它也是一个布局容器,它的每一项就是我们的列表项,每一个...

1043
来自专栏Android干货

Android项目实战(四十七):轮播图效果Viewpager

2939
来自专栏Sorrower的专栏

一起来做个拜年App吧!

972
来自专栏Android开发经验

ScrollView里面基于某个View弹出PopupWindow,PopupWindow不会跟着View滚动?

1742
来自专栏Android干货园

高仿微信朋友圈评论popwindow

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/47...

2231
来自专栏Android源码框架分析

三句代码创建全屏Dialog或者DialogFragment:带你从源码角度实现全屏Dialog

Dialog是APP开发中常用的控件,同Activity类似,拥有独立的Window窗口,但是Dialog跟Activity还是有一定区别的,最明显的就是:默认...

3724
来自专栏Android开发指南

7.侧滑、ViewDragHelper、属性动画

3255
来自专栏Android干货

Android项目实战(三十二):圆角对话框Dialog

3766
来自专栏向治洪

android galley实现画廊效果

今天在做一个软件界面时用到了ImageSwitcher和Gallery控件,在看API时,感觉上面的例子讲的不是很具体,效率并不高。在这里我就以一个图片浏览功能...

2019

扫码关注云+社区

领取腾讯云代金券