你确定自己学会了自定义 MarqueeView?这个你会吗?进来看看吧
含有一个 ImageView 和 TextView
含有两个 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
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();}});
评论