MaterialList源码分析

MaterialList源码解析

项目地址:MaterialList,分析的版本:v3.2.2,Demo 地址:MaterialList Demo

本文结构

  • 1、功能介绍
  • 2、总体设计
  • 3、详细设计
  • 4、MaterialList自定义布局
  • 5、总结

1. 功能介绍

1.1 简介

MaterialList是一个帮助Android开发者获取漂亮CardView的Android库,通过这个库你可以很容易实现具有Material Design风格的ListView,MaterialList中内置了7种类型的CardView,

1.2 如何使用

首先,在你的layout中声明一个MaterialListView:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin">


<com.dexafree.materialList.view.MaterialListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/material_listview"/>


</RelativeLayout>

接着,绑定MaterialListView到一个变量,并设置Item动画

1
2
3
4
mListView = (MaterialListView) findViewById(R.id.material_listview);
mListView.setItemAnimator(new SlideInLeftAnimator());
mListView.getItemAnimator().setAddDuration(300);
mListView.getItemAnimator().setRemoveDuration(300);

然后设置dismiss监听和ItemTouchListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Set the dismiss listener
mListView.setOnDismissCallback(new OnDismissCallback() {
@Override
public void onDismiss(@NonNull Card card, int position) {
// Show a toast
Toast.makeText(mContext, "You have dismissed a " + card.getTag(), Toast.LENGTH_SHORT).show();
}
});
// Add the ItemTouchListener
mListView.addOnItemTouchListener(new RecyclerItemClickListener.OnItemClickListener() {
@Override
public void onItemClick(@NonNull Card card, int position) {
Log.d("CARD_TYPE", "" + card.getTag());
}

@Override
public void onItemLongClick(@NonNull Card card, int position) {
Log.d("LONG_CLICK", "" + card.getTag());
}
});

最后添加Card

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mListView.getAdapter().addAtStart(new Card.Builder(this)
.setTag("BASIC_IMAGE_BUTTONS_CARD")
.setDismissible()
.withProvider(new CardProvider())
.setLayout(R.layout.material_basic_image_buttons_card_layout)
.setTitle("Hi there")
.setDescription("I've been added on top!")
.addAction(R.id.left_text_button, new TextViewAction(this)
.setText("left")
.setTextResourceColor(R.color.black_button))
.addAction(R.id.right_text_button, new TextViewAction(this)
.setText("right")
.setTextResourceColor(R.color.orange_button))
.setDrawable(R.drawable.dog)
.endConfig()
.build());

通过给Provider设置不同的layout,从而获取不同的CardView.
通过设置ListCardProviderR.layout.material_list_card_layout,可以实现带listViewCardView.

2、总体设计

2.1 MaterialList总体由四个部分组成

  • Crad作为整个工程的基本部件,不过Card内容的组装是由Card.Builder和CardProvider(CardProvider实现Observable接口)共同完成,ItemView内View的实例化和给View添加Action都由CardProvider完成。
  • CardLayout 继承自LinearLayout,作为ItemView它被MaterialListAdapter.ViewHolder包裹起来,同时实现Observer接口,监听CardProvider的数据变化
  • MaterialListAdapter继承自RecyclerView.Adapter,通过关联MaterialListView.OnAdapterItemsChanged实现添加ItemView和删除ItemView的回调,通过关联MaterialListView.OnSwipeAnimation实现删除ItemView的动画播放效果,同时实现Observer接口,监听CardProvider的数据变化
  • MaterialListView继承自RecyclerView,通过关联RecyclerItemClickListener,实现ItemView的单击和长按回调,通过关联SwipeDismissRecyclerViewTouchListener实现滑动删除ItemView的回调。

    3、详细设计

    3.1 类关系图

    类关系图
    以上是 MaterialList的主要类的关系图,跟总体设计中介绍的一样大致分为四部分。

    3.2 核心功能介绍

    3.2.1 Card是如何完成内容组装成为ItemView的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final CardProvider provider = new Card.Builder(this)
.setTag("WELCOME_CARD")
.setDismissible()
.withProvider(new CardProvider())
.setLayout(R.layout.material_welcome_card_layout)
.setTitle("Welcome Card")
.setTitleColor(Color.WHITE)
.setDescription("I am the description")
.setDescriptionColor(Color.WHITE)
.setSubtitle("My subtitle!")
.setSubtitleColor(Color.WHITE)
.setBackgroundColor(Color.BLUE)
.addAction(R.id.ok_button, new WelcomeButtonAction(this)
.setText("Okay!")
.setTextColor(Color.WHITE)
.setListener(new OnActionClickListener() {
@Override
public void onActionClicked(View view, Card card) {
Toast.makeText(mContext, "Welcome!", Toast.LENGTH_SHORT).show();
}
}));

我们通过观察上面的代码,首先我们通过new Card.Builder(this)获取Builder实例,接着设置setDismissible(),将属性设置为mDismissible,使它可以移除。

1
2
3
4
5
@NonNull
public Builder setDismissible() {
mDismissible = true;
return this;
}

然后我们通过withProvider(new CardProvider())注入CardProvider实例,此时将获得CardProvider实例,我们通过这个实例设置ItemView的color,layout,title,Action,最后通过provider.endConfig().build()我们就能获取实例Card,整个组合过程就是标准的Builder模式,不过Card就像一个空壳,真正持有ItemView核心属性的是CardProvider。
接着我们回到withProvider()和setLayout()这两个方法,我们知道获取不同风格的CardView就是通过设置不同的layout和CardProvider来实现,但这个布局视图控件是怎样的添加和初始化的呢?
首先我们看MaterialListAdapter的内部类ViewHolder,ViewHolder在类中关联了CardLayout。

1
2
3
4
5
6
7
8
9
10
11
12
public static class ViewHolder extends RecyclerView.ViewHolder {
private final CardLayout view;

public ViewHolder(@NonNull final View v) {
super(v);
view = (CardLayout) v;
}

public void build(Card card) {
view.build(card);//注册观察者
}
}

接着MaterialListAdapter继承RecyclerView.Adapter并重写它的两个方法getItemViewType() onBindViewHolder(),分别绑定LayoutId和绑定holder,并对cardLayout进行初始化。

1
2
3
4
5
6
7
8
9
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.build(getCard(position));//绑定holder,并对cardLayout进行初始化
}

@Override
public int getItemViewType(final int position) {//绑定LayoutId
return mCardList.get(position).getProvider().getLayout();
}

我们看到onBindViewHolder()中调用了holder.build(),holder.build()内又调用了CardLayout的build(),build()内代码如下:

1
2
3
4
5
6
7
8
9
10
public void build(@NonNull final Card card) {
mCard = card;

if (!mObserves) {
mCard.getProvider().addObserver(this);//添加观察者
mObserves = true;
}

mCard.getProvider().render(this, card);//Card视图布局初始化
}

我们可以看到,实际上build()是调用了CardProvider的render()方法来对Card视图布局进行初始化,render()的核心代码如下:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public void render(@NonNull final View view, @NonNull final Card card) {
// card的背景
final CardView cardView = findViewById(view, R.id.cardView, CardView.class);
if (cardView != null) {
cardView.setCardBackgroundColor(getBackgroundColor());
}

// 标题
final TextView title = findViewById(view, R.id.title, TextView.class);
if (title != null) {
title.setText(getTitle());
title.setTextColor(getTitleColor());
title.setGravity(getTitleGravity());
}

// 副标题
final TextView subtitle = findViewById(view, R.id.subtitle, TextView.class);
if (subtitle != null) {
subtitle.setText(getSubtitle());
subtitle.setTextColor(getSubtitleColor());
subtitle.setGravity(getSubtitleGravity());
if (getSubtitle() == null || getSubtitle().isEmpty()) {
subtitle.setVisibility(View.GONE);
} else {
subtitle.setVisibility(View.VISIBLE);
}
}

// 描述内容
final TextView supportingText = findViewById(view, R.id.supportingText, TextView.class);
if (supportingText != null) {
supportingText.setText(getDescription());
supportingText.setTextColor(getDescriptionColor());
supportingText.setGravity(getDescriptionGravity());
}

// 图片
final ImageView imageView = findViewById(view, R.id.image, ImageView.class);
if (imageView != null) {
if (getDrawable() != null) {
imageView.setImageDrawable(getDrawable());
} else {
final RequestCreator requestCreator = Picasso.with(getContext())
.load(getImageUrl());
if (getOnImageConfigListenerListener() != null) {
getOnImageConfigListenerListener().onImageConfigure(requestCreator);
}
requestCreator.into(imageView);
}
}

// Divider
final View divider = findViewById(view, R.id.divider, View.class);
if (divider != null) {
divider.setVisibility(isDividerVisible() ? View.VISIBLE : View.INVISIBLE);

// 如果可见, 将设置分割线的 params
if (isDividerVisible()) {
final ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)
divider.getLayoutParams();
if (isFullWidthDivider()) {
params.setMargins(0, 0, 0, 0);
} else {
int dividerMarginPx = dpToPx(DIVIDER_MARGIN_DP);
params.setMargins(
dividerMarginPx,
0,
dividerMarginPx,
0
);
}
}
}

// Actions
for (final Map.Entry<Integer, Action> entry : mActionMapping.entrySet()) {
final View actionViewRaw = findViewById(view, entry.getKey(), View.class);
if (actionViewRaw != null) {
final Action action = entry.getValue();
action.setProvider(this);
action.onRender(actionViewRaw, card);
}
}
}

上面代码中render()主要完成layout中控件的初始化,和初始化Action,并调用Action的抽象方法onRender(),而Action的子类TextAction,实现了onRender(),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void onRender(@NonNull final View view, @NonNull final Card card) {
TextView textView = (TextView) view;
textView.setText(mActionText != null ? mActionText.toUpperCase(Locale.getDefault()) : null);
textView.setTextColor(mActionTextColor);
textView.setOnClickListener(new View.OnClickListener() {//设置单击监听
@Override
public void onClick(View v) {
if(mListener != null) {
mListener.onActionClicked(view, card);//回调OnActionClickListener的onActionClicked()
}
}
});
}

TextAction在onRender()中获取textview,并设置了单击事件监听,在onClick()中回调OnActionClickListener的onActionClicked()方法,这样使Layout上View只要设置了Action就能添加单击事件回调。通过上面的流程CardLayout将作为MaterialListView的ItemView,并布局到界面上。

3.2.2 如何实现MaterialList上ItemView的滑动移除,

MaterialList可以通过左右滑动移除ItemView,这个功能是很实用的,这个效果是如何实现呢?
首先我们将MaterialListView作为切入口,MaterialListView继承RecyclerView,它通过setOnTouchListener(mDismissListener)设置了TouchListener,这个mDismissListener就是SwipeDismissRecyclerViewTouchListener,它继承View.OnTouchListener类,并重写了onTouch(),以下是onTouch()的核心代码:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mRecyclerView.getWidth();
}

switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
return false;
}

// TODO: ensure this is a finger, and set a flag

// 找到被触控的child view(执行命中测试)
Rect rect = new Rect();
int childCount = mRecyclerView.getChildCount();
int[] listViewCoords = new int[2];
mRecyclerView.getLocationOnScreen(listViewCoords);//一个控件在其整个屏幕上左上点的坐标,保存到listViewCoords中,以屏幕左上点为原点
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {//遍历child view
child = mRecyclerView.getChildAt(i);
child.getHitRect(rect);//得到rect,它有child view的左上点坐标(left,top)和右下点坐标(right,bottom)
if (rect.contains(x, y)) {//判断命中点(x,y)是否在ret中,如果是则说明该点落在这个child view上,
mDownView = child;
break;
}
}

if (mDownView != null) {
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
mDownPosition = mRecyclerView.getChildPosition(mDownView);
if (mCallbacks.canDismiss(mDownPosition)) {//设置该mDownView可以滑动移除
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);//对触摸进行速度跟踪
} else {
mDownView = null;
}
}
return false;
}

case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}

if (mDownView != null && mSwiping) {
// cancel
animate(mDownView)
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}

case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}

float deltaX = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);//1s内的速度
float velocityX = mVelocityTracker.getXVelocity();//x轴的1s内的速度
float absVelocityX = Math.abs(velocityX);//返回绝对值
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) {//x轴方向滑动的距离大于child宽同时mSwiping==true
dismiss = true;
dismissRight = deltaX > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX && mSwiping) {
//只有在同一个方向进行扔或拖是,dismiss才有效的
dismiss = (velocityX < 0) == (deltaX < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss && mDownPosition != ListView.INVALID_POSITION) {
// 移除
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
++mDismissAnimationRefCount;
animate(mDownView)
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
performDismiss(downView, downPosition);//在动画结束时,移除downView
}
});
} else {
// 取消
animate(mDownView)
.translationX(0)
.alpha(1)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}

case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused) {
break;
}

mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
float deltaY = motionEvent.getRawY() - mDownY;
if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {//x轴方向滑动的距离大于最小有效滑动距离,同时x轴方向滑动的距离的二分之一大于Y轴方向滑动的距离,则对事件进行拦截
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
mRecyclerView.requestDisallowInterceptTouchEvent(true);//解决滑动冲突,内部拦截法

// Cancel ListView's touch (un-highlighting the item)
MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |(motionEvent.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
mRecyclerView.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}

if (mSwiping) {//播放动画,child view沿滑动x轴方向移动,并设置alpha
setTranslationX(mDownView, deltaX - mSwipingSlop);
setAlpha(mDownView, Math.max(0f, Math.min(1f,
1f - 2f * Math.abs(deltaX) / mViewWidth)));
return true;
}
break;
}
}
return false;
}

我们知道一个完整的滑动事件,是先ACTION_DOWN,接着ACTION_MOVE,最后才ACTION_UP,我们将从这三方面分析onTouch(),

  • 首先在ACTION_DOWN中我们获取触控点坐标,接着我们遍历mRecyclerView的childView,计算出这个触控点落在哪一个childView上,从而获取downView和position
  • 然后在ACTION_MOVE中我们通过判断触控点在x轴方向滑动的距离大于最小有效滑动距离和x轴方向滑动的距离的二分之一大于Y轴方向滑动的距离,则设置mSwiping = true,并对事件进行拦截,同时播放属性动画,使mDownView沿滑动x轴方向移动,并设置alpha
  • 最后在ACTION_UP中通过mVelocityTracker.getXVelocity()获取触控点1s内在x轴方向的速度absVelocityX,如果absVelocityX大于等于最小速度值和小于或等于最大速度值,absVelocityX大于y轴方向速度,且mSwiping为true,这些条件都满足的话,则设置dismiss = true;或是如果x轴方向滑动的距离大于二分之一downView宽时,且mSwiping==true,则设置dismiss = true。接下来我们设置dismiss动画,并在动画结束时,调用performDismiss()。而我们downView移除的时候下面的view会慢慢的往上面顶,填补这个空白,这个是如何做到的呢?核心就是performDismiss()内设置的属性动画,其核心代码如下:
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
private void performDismiss(final View dismissView, final int dismissPosition) {
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalHeight = dismissView.getHeight();

ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);//设置逐渐originalHeight到1

animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//省略代码
mCallbacks.onDismiss(mRecyclerView, dismissPositions);
//回调onDismiss()来将downView从adapter中移除
//省略代码
// 发送一个 cancel event,来释放资源
long time = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(time, time,
MotionEvent.ACTION_CANCEL, 0, 0, 0);
mRecyclerView.dispatchTouchEvent(cancelEvent);

mPendingDismisses.clear();

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
});

我们从上面的代码可以看出通过ValueAnimator来逐渐将downView的高度从originalHeight到1,实现下面的view慢慢往上顶的动画效果。在动画结束时,会调用onDismiss()来将downView从adapter中移除,同时发送一个 cancel event,重置参数和释放资源。

3.2.3 观察者模式在MaterialList中的应用

从3.1的类关系图我们可以知道MaterialListAdapter和CardLayout都实现了Observer接口,CardProvider继承Observable类,CardProvider注册观察者过程如下,首先在MaterialListAdapter 中的onBindViewHolder(),代码如下:

1
2
3
4
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
holder.build(getCard(position));
}

holder会调用build(),方法内又会调用CardLayout的build(),我们看下面CardLayout的build()的方法代码

1
2
3
4
5
6
7
8
9
10
public void build(@NonNull final Card card) {
mCard = card;

if (!mObserves) {
mCard.getProvider().addObserver(this);//注册CardLayout观察者
mObserves = true;
}

mCard.getProvider().render(this, card);//Card视图布局初始化
}

从上面代码可以看出CardProvider注册了CardLayout观察者,而在MaterialListAdapter中的add(),同样给CardProvider注册了MaterialListAdapter观察者。代码如下:

1
2
3
4
5
6
public void add(final int position, @NonNull final Card card, final boolean scroll) {
mCardList.add(position, card);
card.getProvider().addObserver(this);//注册MaterialListAdapter观察者
mItemAnimation.onAddItem(position, scroll);
notifyItemInserted(position); // Triggers the animation!
}

这样每当我们CardProvider调用setTitle()等设置属性的方法时,就会调用notifyObservers()时,代码如下:

1
2
3
4
5
public T setTitle(@NonNull final String title) {
mTitle = title;
notifyDataSetChanged();
return (T) this;
}

这样会触发CardLayout和MaterialListAdapter的回调函数update()

1
2
3
4
5
6
7
8
@Override
//CardLayout重写的update()
public void update(final Observable observable, final Object data) {
if(data == null) {
build(mCard);//初始化card,接下来调用render()来初始化LayoutId内的控件
((CardProvider) observable).notifyDataSetChanged(getCard());
}
}

1
2
3
4
5
6
7
8
9
10
@Override
//MaterialListAdapter重写的update()
public void update(final Observable observable, final Object data) {
if (data instanceof DismissEvent) {
remove(((DismissEvent) data).getCard(), true);//data为DismissEvent类型则移除这个card
}
if (data instanceof Card) {
notifyDataSetChanged();//通知adapter数据已经改变,重新布局绘制MaterialView内的view
}
}

通过这个观察者模式,我们可以很轻松地在修改CardProvider的Title,TitleColor,Drawable后,实时的在MaterialListView显示出刚刚的修改。

4、 MaterialList自定义布局

MaterialList库有7个layout 的xml文件,已经足够我们使用了,不过我们可不可以设置自己的XML文件进去呢?回答肯定是可以的,MaterialList就有很好的扩展性,通过本文的第三部分,我们知道了Card的布局控件初始化是在CardProvider的render()完成的,单击事件的添加是通过Action,这样我们要实现imageView的单击事件,就需要通过继承TextViewAction来事件,详细文件如下:
layout_new.xml

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
68
69
70
71
72
73
74
75
76
77
78
<?xml version="1.0" encoding="utf-8"?>

<com.dexafree.materialList.card.CardLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/MainLayout">


<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardView"
style="@style/Material_Card_View"
card_view:cardCornerRadius="@dimen/card_corner_radius"
card_view:cardElevation="@dimen/card_elevation">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<FrameLayout android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:contentDescription="@null"/>


<TextView
android:id="@+id/title"
style="@style/Material_Card_Title"
android:layout_gravity="bottom"
tools:text="Title"/>

</FrameLayout>

<TextView
style="@style/Material_Card_Subtitle_24dp"
android:id="@+id/supportingText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="@dimen/big_padding"
android:textColor="@color/description_color"
android:textSize="@dimen/description_size"
tools:text="Test description"/>


<include layout="@layout/divider"/>

<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:orientation="horizontal">


<ImageView
android:id="@+id/share_text_button"
style="@style/Material_Action"
android:clickable="true"
tools:text="Action 1"
android:src="@drawable/share_text_button"/>


<ImageView
android:id="@+id/mark_text_button"
style="@style/Material_Action"
tools:text="Action 2"
android:clickable="true"
android:src="@drawable/mark_text_button"/>

<ImageView
android:id="@+id/star_text_button"
style="@style/Material_Action"
android:clickable="true"
tools:text="Action 3"
android:src="@drawable/star_text_button"/>

</LinearLayout>

</LinearLayout>

</android.support.v7.widget.CardView>

</com.dexafree.materialList.card.CardLayout>

ImageViewAction继承于TextViewAction,使添加的ImageView可以添加监听点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ImageViewAction extends TextViewAction{
@Nullable
private OnActionClickListener mListener;

public ImageViewAction(@NonNull Context context) {
super(context);
}

@Override
protected void onRender(@NonNull final View view, @NonNull final Card card) {
ImageView imageView = (ImageView) view;

imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mListener != null) {
mListener.onActionClicked(view, card);
}
}
});
}
}

这样我们就可以构建新的Card

1
2
3
4
5
6
7
8
9
10
11
final Card card =new Card.Builder(this)
.setTag("IMAGE_BUTTONS_CARD")
.setDismissible()
.withProvider(new CardProvider())
.setLayout(R.layout.layout_new)
.setTitle("Hi there")
.setDescription("I've been added on top!")
.addAction(R.id.share_text_button, new ImageViewAction(this))
.addAction(R.id.star_text_button, new ImageViewAction(this))
.addAction(R.id.mark_text_button, new ImageViewAction(this))
.setDrawable(R.drawable.photo).endConfig().build();

效果图如下:

这里写图片描述

5、总结

到此为止,整个MaterialList基本分析完了,从介绍使用到深入分析源码设计,最后介绍如何扩展,这个过程就像一次次历险,每一次发现都有不一样的收获,今后还会继续写这类型的博客,继续read the fucking source code!!!

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章