Android 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:

<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动画

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

然后设置dismiss监听和ItemTouchListener

// 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

 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的

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,使它可以移除。

@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。

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进行初始化。

@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()内代码如下:

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()的核心代码如下:

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(),如下:

@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()的核心代码:

@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()内设置的属性动画,其核心代码如下:
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(),代码如下:

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

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

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观察者。代码如下:

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()时,代码如下:

public T setTitle(@NonNull final String title) {
        mTitle = title;
        notifyDataSetChanged();
        return (T) this;
    } 

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

@Override
    //CardLayout重写的update()
    public void update(final Observable observable, final Object data) {
        if(data == null) {
            build(mCard);//初始化card,接下来调用render()来初始化LayoutId内的控件
            ((CardProvider) observable).notifyDataSetChanged(getCard());
        }
    }
@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

<?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可以添加监听点击事件

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

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基本分析完了,从介绍使用到分析源码设计,最后介绍如何扩展,这个过程就像一次次历险,每一次发现都有不一样的收获,今后还会继续写这类型的博客,希望对大家有所帮助。

你可能感兴趣的:(源码,android,Android开发,设计)