今天博客的主要内容是两个常见的自定义控件,第1个是我们常常看到的点击隐藏点击查看控件,第2个控件是仿微信朋友圈的9宫格图片控件,相对上1篇的流布式布局来讲,这篇博客更容易,只不过触及更多的知识点而已
转载请注明原博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/51772308
图2源码下载地址
图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();
}
}
```
```
<?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)首先我们自己自定义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()方法,基本所有的逻辑都放在这里
/**
* 博客地址: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源码下载地址: