今天博客的主要內容是兩個常見的自定義控件,第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源碼下載地址: