程序员人生 网站导航

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

栏目:综合技术时间:2016-07-04 16:42:41

自定义View经常使用例子2(点击展开隐藏控件,9宫格图片控件)

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

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

1.空话不多说了,先来看1下效果图

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


  1. 图2效果,类似于朋友圈9宫格图片


图2源码下载地址

图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 = ⑴80; 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. 如图所示,图逐一4个部份组成,数字,标题,箭头,图片,点击标题所在的那1行,图片回相应地隐藏或显示。
  2. 数字,标题,箭头都在同1个相对布局里面,图片在单独的1个相对布局中,整体由 LinearLayout构成,布局文件以下

```

<?xml version="1.0" encoding="utf⑻"?> <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开始不用加载这个相对局部,这样它不会占用位置

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 = ⑴80; expand(mContentRelativeLayout); } else { degree = 0; mArrowImageView.setTag(true); collapse(mContentRelativeLayout); } mArrowImageView.animate().setDuration(duration).rotation(degree);

我们在来看1下在expand我们做了甚么,其实就是给contentRelativeLayout履行1个动画,在动画的履行进程中不断改变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();

反之,隐藏也是履行1个动画,不断改变高度,只不太高度 是愈来愈小,直至为0为止

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


图2源码分析

源码下载地址

1.思路解析,

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

public void setImageUrl(String url);

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

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

2)接着我们自定义1个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()); //这里做1个重用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; } }

图2源码下载地址

图1源码下载地址:

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐