写点什么

你确定自己学会了自定义 MarqueeView?这个你会吗?进来看看吧

用户头像
Android架构
关注
发布于: 刚刚
  1. 含有一个 ImageView 和 TextView

  2. 含有两个 TextView 和 一个 ImageView


要想实现上述效果,需要两个步骤:


第一步:继承于 ItemViewDelegate,重写 getItemViewLayoutId,isForViewType,convert 方法,其中 getItemViewLayoutId 方法表示返回布局 layoutId,convert 方法在刷新当前 View 的时候会调用,可以用来刷新数据


/**


  • Created by xujun on 1/9/2018.*/public class TextItemViewDelegate implements ItemViewDelegate<MultiTypeBean> {@Overridepublic int getItemViewLayoutId() {return R.layout.item_simple_text;}


@Overridepublic boolean isForViewType(MultiTypeBean item, int position) {return item.mItemViewType == MultiTypeBean.ItemViewType.text;}


@Overridepublic void convert(ViewHolder holder, MultiTypeBean multiTypeBean, int position) {TextView tv = holder.getView(R.id.tv);tv.setText(multiTypeBean.title);}}


public class ImageTextItemViewDelegate implements ItemViewDelegate<MultiTypeBean> {


@Overridepublic int getItemViewLayoutId() {return R.layout.item_image_text;}


@Overridepublic boolean isForViewType(MultiTypeBean item, int position) {return item.mItemViewType == MultiTypeBean.ItemViewType.imageText;}


@Overridepublic void convert(ViewHolder holder, MultiTypeBean multiTypeBean, int position) {TextView tv = holder.getView(R.id.tv);tv.setText(multiTypeBean.title);


ImageView iv = holder.getView(R.id.iv);iv.setImageResource(multiTypeBean.resImageId);}}


public class MultiTextItemViewDelegate implements ItemViewDelegate<MultiTypeBean> {


@Overridepublic int getItemViewLayoutId() {return R.layout.item_multi_text;}


@Overridepublic boolean isForViewType(MultiTypeBean item, int position) {return item.mItemViewType == MultiTypeBean.ItemViewType.multiTextAndImage;}


@Overridepublic void convert(ViewHolder holder, MultiTypeBean multiTypeBean, int position) {TextView tv = holder.getView(R.id.tv);tv.setText(multiTypeBean.title);


TextView tvContent = holder.getView(R.id.tv_content);tvContent.setText(multiTypeBean.content);


ImageView iv = holder.getView(R.id.iv);iv.setImageResource(multiTypeBean.resImageId);}}


第二步:将 ItemViewDelegate 添加到 MultiItemTypeAdapter 中,并给 marqueeView 设置 Adapter。


MultiItemTypeAdapter<MultiTypeBean> multiItemTypeAdapter = new MultiItemTypeAdapter<MultiTypeBean>(mContext, datas);multiItemTypeAdapter.addItemViewDelegate(new TextItemViewDelegate());multiItemTypeAdapter.addItemViewDelegate(new ImageTextItemViewDelegate());multiItemTypeAdapter.addItemViewDelegate(new MultiTextItemViewDelegate());multiItemTypeAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {@Overridepublic void onItemClick(int position, View view) {Log.i(TAG, "onItemClick: position = " + position);if (marqueeView.isStart()) {marqueeView.stopFilp();} else {marqueeView.startFlip();}}});marqueeView.setAdapter(multiItemTypeAdapter);

其他用法

  • 设置布局的对齐方向:


void setGravity(int gravity)


  • 设置动画的方向:


void setDirection(int direction)


  • 设置动画的执行时间:(内置动画支持,自定义动画不支持)


void setAnimDuration(int animDuration)


  • 设置两个 View 的轮播间隔


void setInterval(int interval)


  • 设置进入进出动画(即自定义动画)


setInAndOutAnimation(Animation inAnimation, Animation outAnimation)


  • 设置 Flip 监听


void setIFlipListener(IFlipListener IFlipListener)


mMvMultiText5.setIFlipListener(new MarqueeView.IFlipListener() {@Overridepublic void onFilpStart(int position, View view) {Log.i(TAG, "onFilpStart: position = " + position);}


@Overridepublic void onFilpSelect(int position, View view) {Log.i(TAG, "onFilpSelect: position = " + position);}});


  • 设置点击事件监听


simpleTextAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {@Overridepublic void onItemClick(int position, View view) {Log.i(TAG, "onItemClick: position = " + position);if (marqueeView.isStart()) {marqueeView.stopFilp();} else {marqueeView.startFlip();}}});


当然,以上功能也支持自定义属性:


自定义属性说明


原理篇

原理也主要分为两个部分第一部分:MultiItemTypeAdapter 相关


第二部分:MarqueeView 与 MultiItemTypeAdapter 之间实质怎样建立联系的


MultiItemTypeAdapter 讲解讲解 MultiItemTypeAdapter 之前,我们先来看一下相应的接口 ItemViewDelegate 和类 ItemViewDelegateManager


  • ItemViewDelegate


而 ItemViewDelegateManager 主要是管理 ItemViewDelegate 的。


public interface ItemViewDelegate<T> {


public abstract int getItemViewLayoutId();


public abstract boolean isForViewType(T item, int position);


public abstract void convert(ViewHolder holder, T t, int position);


}


ItemViewDelegate 主要有三个方法,getItemViewLayoutId 方法表示获取 ItemViewLayoutId,isForViewType 会根据 item 即 position 判断当前的 item 是不是属于当前的 ItemViewDelegate,convert 在刷新当前 item 的时候会调用。


  • ItemViewDelegateManager


ItemViewDelegateManager,没错,从字面意思来看,就是来管理 ItemViewDelegate 的。接下来我们来看 ItemViewDelegateManager 里面几个比较重要的方法。


  • 当有指定 viewType 会先去缓存里面查找是否存在相应的 delegate,如果存在,不合法,抛出异常。因为同一时刻只有一个 delegate 能处理该 position;

  • 当没有指定 viewType 的时候,我们会以当前 delegates 的容量作为 key 存进 SparseArrayCompat 中。


SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat();


public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate) {if (delegates.get(viewType) != null) {throw new IllegalArgumentException("An ItemViewDelegate is already registered for the" +" viewType = " + viewType + ". Already registered ItemViewDelegate is " +delegates.get(viewType));}delegates.put(viewType, delegate);return this;}


public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate) {int viewType = delegates.size();if (delegate != null) {delegates.put(viewType, delegate);viewType++;}return this;}


因此,我们如果想获取对应 position 的 viewType,可以通过 delegate 在 delegates 中对应的 key


于是衍生出以下方法:


即根据当前 postion,去查找相应的 delegate,然后再获取通过 delegate 在 delegates 数组中对应的 key,即我们的 v


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


iewType


public int getItemViewType(T item, int position) {int delegatesCount = delegates.size();for (int i = delegatesCount - 1; i >= 0; i--) {ItemViewDelegate<T> delegate = delegates.valueAt(i);if (delegate.isForViewType(item, position)) {return delegates.keyAt(i);}}throw new IllegalArgumentException("No ItemViewDelegate added that matches position=" +position + " in data source");}

MultiItemTypeAdapter 讲解

主要有几个重要的方法:


public View createItemView(ItemViewDelegate<T> itemViewDelegate, ViewGroup parent) {int layoutId = itemViewDelegate.getItemViewLayoutId();ViewHolder viewHolder = null;View convertView = LayoutInflater.from(mContext).inflate(layoutId, parent, false);viewHolder = new ViewHolder(mContext, convertView, parent, -1);viewHolder.mLayoutId = layoutId;onViewHolderCreated(viewHolder, viewHolder.getConvertView());return convertView;}


public View createItemView(int position, View convertView, ViewGroup parent) {ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(mDatas.get(position), position);int layoutId = itemViewDelegate.getItemViewLayoutId();ViewHolder viewHolder = null;if (convertView == null) {convertView = LayoutInflater.from(mContext).inflate(layoutId, parent, false);viewHolder = new ViewHolder(mContext, convertView, parent, position);viewHolder.mLayoutId = layoutId;onViewHolderCreated(viewHolder, viewHolder.getConvertView());} else {viewHolder = (ViewHolder) convertView.getTag();viewHolder.mPosition = position;}convert(viewHolder, getItem(position), position);return convertView;}


private void convert(ViewHolder viewHolder, T item, int position) {mItemViewDelegateManager.convert(viewHolder, item, position);}


public SparseArrayCompat<View> getAllTyeView(ViewGroup parent) {SparseArrayCompat<ItemViewDelegate<T>> itemViewDelegates = getItemViewDelegate();int size = itemViewDelegates.size();SparseArrayCompat<View> viewSparseArrayCompat = new SparseArrayCompat<>();for (int i = 0; i < size; i++) {ItemViewDelegate delegate = itemViewDelegates.valueAt(i);View itemView = createItemView(delegate, parent);int itemViewType = getItemViewType(itemViewDelegates, i);Log.i(TAG, "getAllTyeView: itemViewType = " + itemViewType);viewSparseArrayCompat.put(itemViewType, itemView);}return viewSparseArrayCompat;}


第一个方法: createItemView(ItemViewDelegate itemViewDelegate, ViewGroup parent),会根据传递的 itemViewDelegate 创建相应的 convertView,并调用*onViewHolderCreated() 方法


第二个方法:createItemView 会根据传递进来的 position 创建相应的 convertView 。而这个 convertView 什么时候为 null,什么时候不为 null,这个必须要外部调用来管理,MultiItemTypeAdapter 管理不了,也不应该管理。


第三个方法: getAllTyeView ,这个方法会遍历所有的 itemViewDelegate 并创建相应的 View 及 ViewHolder


接下来我们来看一下在 MarqueeView 里面是怎样实现 convertView 的缓存的,标重点了。


MarqueeView


首先我们来看一下 getItemView


private SparseArray<View> mViews;


private View getItemView(int index) {int itemViewType = mMultiItemTypeAdapter.getItemViewType(index);// 获取缓存的 convertViewView convertView = mViews.get(itemViewType);View itemView = mMultiItemTypeAdapter.createItemView(index, convertView, MarqueeView.this);return itemView;}


从代码中可以看出我们是从 mViews 里面根据当前位置 index 的 itemViewType 取出 convertView 的。那我们的 mViews 是什么时候赋值的呢?


是在 addAllTypeView 方法中


private void addAllTypeView() {int viewTypeCount = mMultiItemTypeAdapter.getViewTypeCount();if (viewTypeCount < 1) {return;}mViews.clear();SparseArrayCompat<View> allTyeView = mMultiItemTypeAdapter.getAllTyeView(MarqueeView.this);int curItemViewType = mMultiItemTypeAdapter.getItemViewType(mPosition);for (int i = 0; i < allTyeView.size(); i++) {int key = allTyeView.keyAt(i);View view = allTyeView.valueAt(i);mViews.put(key, view);LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);layoutParams.gravity = mGravity;addView(view, layoutParams);// 设置当前 itemView 可见,其他不可见 if (key == curItemViewType) {view.setVisibility(View.VISIBLE);} else {view.setVisibility(View.INVISIBLE);}}}


在 addAllTypeView 的时候,会调用 mMultiItemTypeAdapter.getAllTyeView 初始化所有类型的 itemView,并添加到 mViews 缓存,key 为 viewType,value 为 itemView。

MarqueeView 是怎样与 MultiItemTypeAdapter 建立关联的

我们来看一下 setAdapter 这个方法


有一个参数,MultiItemTypeAdapter ,这个 MultiItemTypeAdapter 主要是用来实现 View 的复用以及根据不同的 viewType 添加不同的 View 的。这里先大概有个印象。下面会讲解到。


public void setAdapter(MultiItemTypeAdapter multiItemTypeAdapter) {if (multiItemTypeAdapter == null) {return;}mMultiItemTypeAdapter = multiItemTypeAdapter;start(mInAnimResId, mOutAnimResId);}


private void start(final @AnimRes int inAnimResId, final @AnimRes int outAnimResID) {// 第一步:做一些重置的工作,mPosition 终止,清除所有 View,清除动画;mPosition = 0;clearAnimation();removeAllViews();


// 第二步:根据 MultiItemTypeAdapter ,把所有类型的 typeView 加载进来,并根据 mPosition 设置可见性 addAllTypeView();


// 第三步:初始化当前 position 的 View,并调用 mMultiItemTypeAdapter 的相关方法 int itemViewType = mMultiItemTypeAdapter.getItemViewType(mPosition);View convertView = mViews.get(itemViewType);View itemView = mMultiItemTypeAdapter.createItemView(mPosition, convertView, MarqueeView.this);mCurView = itemView;mLastView = mCurView;


// 利用 handle 发送消息,执行动画 post(new Runnable() {@Overridepublic void run() {sendAppear();}});

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
你确定自己学会了自定义MarqueeView?这个你会吗?进来看看吧