自定义view(二)

  • content
    {:toc}

    0x01

最近公司要求要出一个暂开内容,然后还能收缩内容的控件

其实一开始我想用ExpendListView,但是后面寻思着用了自定义View,毕竟现在自己也在学这一块

0x02 效果

0x03 代码

一、SubExpendLayout代码

SubExpendLayout 这个是上面那个view,上面那个view实际上是叠加了一个textView,然后进行的,因为需要要求第一个textView收缩之后要点点,所以加上了singleLine =true

根据分析

对应的expend()close()代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void expend() {
tvContentClose.setVisibility(INVISIBLE); //close的textview
ivExpend.setVisibility(INVISIBLE);//close展开按钮图片

tvContentExpend.setVisibility(VISIBLE);//展开的textview
ivClose.setVisibility(VISIBLE);//关闭的按钮图片
}

public void close() {
tvContentClose.setVisibility(VISIBLE);
ivExpend.setVisibility(VISIBLE);

tvContentExpend.setVisibility(INVISIBLE);
ivClose.setVisibility(INVISIBLE);
}

对应的测量代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

measureChildren(widthMeasureSpec, heightMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int width = 0;
int height = 0;

switch (widthMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST: { //宽度我这里实际默认了屏幕宽度
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
int measuredWidth = view.getMeasuredWidth();
width = Math.max(widthSize, measuredWidth);
}
}
break;
default: {
width = widthSize;
}
break;
}

switch (heightMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST: {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);

//隐藏的就不进行测量了
if (view.getVisibility() != GONE) {
int measuredHeight = view.getMeasuredHeight();
if (i > 0) { //第一个就照着第二个展开内容的摆放就好了,因为第一个内容和第二个的内容相同,但是是单行的,所以第二个的内容肯定比第一个打
height += measuredHeight;
}
}
}
}
break;
default: {
height = heightSize;
}
break;
}

Log.e(TAG, "onMeasure: " + height);
setMeasuredDimension(width, height);

}

对应的onLayout的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

int tempHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getVisibility() == GONE) {
//隐藏的就下一个,就不在布局
continue;
}
int measuredHeight = view.getMeasuredHeight();
view.layout(0, tempHeight, view.getMeasuredWidth(), measuredHeight + tempHeight);
//第二个和第一个重叠,所以不进行高度添加
if (i > 0) {
tempHeight += measuredHeight;
} else {
//记录这个singleLine的textView
closeHeight = measuredHeight;
}
}

//这里应该执行动画的时候多次执行,所以只能得到第一次的值就是我们想要的
if (expendHeight == 0 || reLayout) {
expendHeight = getMeasuredHeight();

midHeight = (closeHeight + expendHeight) / 2;

reLayout = false;
}
}

最后可以来一个触摸事件的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (isExpend) {
closeLayout(); //执行关闭动画

} else {
expendLayout();//执行展开动画

}
break;
default:
break;
}

return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public void expendLayout() {
ValueAnimator animator = ValueAnimator.ofInt(closeHeight, expendHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int changeHeight = (int) animation.getAnimatedValue();
LayoutParams layoutParams = getLayoutParams();
layoutParams.height = changeHeight;
setLayoutParams(layoutParams);

if (changeHeight >= midHeight) {
expend();
}
}
});
animator.setDuration(500);
animator.start();

isExpend = true;
}

public void closeLayout() {
ValueAnimator animator = ValueAnimator.ofInt(expendHeight, closeHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int changeHeight = (int) animation.getAnimatedValue();
LayoutParams layoutParams = getLayoutParams();
layoutParams.height = changeHeight;
setLayoutParams(layoutParams);

if (changeHeight <= midHeight) {
close();
}
}
});
animator.setDuration(500);
animator.start();

isExpend = false;
}

具体的动画实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public void expendLayout() {
ValueAnimator animator = ValueAnimator.ofInt(closeHeight, expendHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int changeHeight = (int) animation.getAnimatedValue();
LayoutParams layoutParams = getLayoutParams();
layoutParams.height = changeHeight;
setLayoutParams(layoutParams);

if (changeHeight >= midHeight) {
expend();
}
}
});
animator.setDuration(500);
animator.start();

isExpend = true;
}

public void closeLayout() {
ValueAnimator animator = ValueAnimator.ofInt(expendHeight, closeHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int changeHeight = (int) animation.getAnimatedValue();
LayoutParams layoutParams = getLayoutParams();
layoutParams.height = changeHeight;
setLayoutParams(layoutParams);

if (changeHeight <= midHeight) {
close();
}
}
});
animator.setDuration(500);
animator.start();

isExpend = false;
}

二、RecordExpendLayout代码

分别设置了2个类,SubmissionExpendAdapter是个实现类

RecordExpendLayout的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public RecordExpendLayout(Context context, AttributeSet attrs) {
super(context, attrs);

init();
}


private void init() {

//设置了个背景
setBackgroundColor(getResources().getColor(R.color.background));

//把点击关闭和开启状态的title提前加入了进来
View titleView = LayoutInflater.from(getContext()).inflate(R.layout.view_record_expend_title, this, false);
cardItemViews.add(titleView);
addView(titleView);

ivCtrolStatus = (ImageView) findViewById(R.id.iv_ctrol_status);

setStatus(Status.EXPEND);
}

进行对应的测量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// measureChildren(widthMeasureSpec, heightMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
}

int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int width = 0;
int height = 0;

switch (widthMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST: {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
int measuredWidth = view.getMeasuredWidth();
width = Math.max(widthSize, measuredWidth);
}
}
break;
default: {
width = widthSize;
}
break;
}

switch (heightMode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST: {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);

MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();

//隐藏的就不进行测量了
if (view.getVisibility() != GONE) {
int measuredHeight = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
height += measuredHeight;
}
}
}
break;
default: {
height = heightSize;
}
break;
}

Log.e(TAG, "onMeasure: " + height);
// int currentHeight = ViewUtil.dp2px(getContext(), 20);

if (expendHeight == 0 || reLayout) {
expendHeight = getMeasuredHeight();
}
setMeasuredDimension(width, height);

}

进行onLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

int tempHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getVisibility() == GONE) {
//隐藏的就下一个,就不在布局
continue;
}
int measuredHeight = view.getMeasuredHeight();
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();

view.layout(lp.leftMargin, tempHeight + lp.topMargin, view.getMeasuredWidth() + lp.rightMargin, measuredHeight + tempHeight + lp.topMargin - lp.bottomMargin);

tempHeight += measuredHeight + lp.topMargin + lp.bottomMargin;
//记录这个singleLine的textView
if (i == 0) {
closeHeight = measuredHeight;
}
}

//这里应该执行动画的时候多次执行,所以只能得到第一次的值就是我们想要的
if (expendHeight == 0 || reLayout) {
expendHeight = getMeasuredHeight();

midHeight = (closeHeight + expendHeight) / 2;

reLayout = false;

}
Log.e(TAG, "onLayout: expendHeight: " + expendHeight + " " + getMeasuredHeight());
}

剩下的就是ExpendAdapter 接口的设置了

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ExpendAdapter {

int getItemCount();

/**
* 已经在内部添加,因为是已知 item
*
* @param position
* @param view
* @param parent
*/
void getView(int position, View view, RecordExpendLayout parent);
}

RecordExpendLayout和adapter相关的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}


private ExpendAdapter mAdapter;

public void setAdapter(ExpendAdapter adapter) {
mAdapter = adapter;

notifyAllData();
}

private void handleData() {
int itemCount = mAdapter.getItemCount();
for (int i = 0; i < itemCount; i++) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.view_record_expend_item, this, false);
cardItemViews.add(view);
addView(view);

mAdapter.getView(i, view, this);

}
}

public void notifyAllData() {
if (mAdapter != null) {
handleData();
}

Log.e(TAG, "notifyAllData: ");
//重新加入值,就重新测量一次
reLayout = true;
requestLayout();
}

以上就是上周觉得比较有意思而又比较重要的代码