




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、打造炫酷通用的ViewPager指示器 - Adapter模式适配所有用Adapter适配器模式适配所有的效果,堪称终结者效果实现2.1 整合上一个实例:我还是还是拿上一个实例来做演示吧。这里我贴几种常见的效果,首先声明Android自带的有这个控件叫TabLayout,大家可以自己用用试试看好用不?我也用过但是不做任何评价,自己造的轮子还是想怎么用就怎么用。还有一些奇葩的效果如每个头部Item布局不一样,还有上面是图片下面是文字选中的效果各不相同等等,我们都要去适配。 2.2 实现思路:我在老早的时候用过ViewPageIndicator,还没毕业出来工作的时候,好不好用我也不做评价,就是那
2、个时候搞了一晚上没搞出来第二天一看原来是activity的Theme主题没有配置,大家手上肯定也有类似的效果也都可以用,只是以个人的理解来自己造一个轮子。 2.2.1 控件肯定是继承ScrollView因为可以左右滑动,如果再去自定义ViewGroup肯定不划算。 2.2.2 怎样才能适合所有的效果,难道我们把所有可能出现的效果都写一遍吗?这的确不太可能,所以肯定采用Adapter适配器模式。 2.2.3 我们先动起来从简单的入手,先做到动态的添加不同的布局条目再说吧。 2.3 自定义TrackIndicatorView动态添加布局:这里为了适配所有效果,所以决定采用适配器Adapter设计模
3、式,上面也提到过。至于什么是适配器模式大家需要看一下这个1. 模式介绍1.1模式的定义:适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。1.2模式的使用场景:用电源接口做例子,笔记本电脑的电源一般都是接受5V的电压,但是我们生活中的电线电压一般都是220V的输出。这个时候就出现了不匹配的状况,在软件开发中我们称之为接口不兼容,此时就需要适配器来进行一个接口转换。在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个层我们可以理解为这里的Adapter层,通过这层来进行一个接口转换就达到了兼容的目的。 2.
4、模式的简单实现.1简单实现的介绍:在上述电源接口这个示例中,5V电压就是Target接口,220v电压就是Adaptee类,而将电压从220V转换到5V就是Adapter。.2类适配器模式: /* * Target角色 */public interface FiveVolt public int getVolt5();/* * Adaptee角色,需要被转换的对象 */public class Volt220 public int getVolt220() return 220; / adapter角色public class ClassAdapter extends Volt220 impl
5、ements FiveVolt Override public int getVolt5() return 5; Target角色给出了需要的目标接口,而Adaptee类则是需要被转换的对象。Adapter则是将Volt220转换成Target的接口。对应的是Target的目标是要获取5V的输出电压,而Adaptee即正常输出电压是220V,此时我们就需要电源适配器类将220V的电压转换为5V电压,解决接口不兼容的问题。public class Test public static void main(String args) ClassAdapter adapter = new ClassA
6、dapter(); System.out.println("输出电压 : " + adapter.getVolt5(); .3.Android源码中的模式实现与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用代理关系连接到Adaptee类。 从图2可以看出,Adaptee类 ( Volt220 ) 并没有getVolt5()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装类Adapter。这个包装类包装了一个Adapte
7、e的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。/* * Target角色 */public interface FiveVolt public int getVolt5();/* * Adaptee角色,需要被转换的对象 */public class Volt220 public int getVolt220() return 220; / 对象适配器模式public class ObjectAdapter implements FiveVolt Volt220 mVolt220; pu
8、blic ObjectAdapter(Volt220 adaptee) mVolt220 = adaptee; public int getVolt220() return mVolt220.getVolt220(); Override public int getVolt5() return 5; .4.类适配器和对象适配器的权衡*类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组合的方式,是动态组合的方式。*对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了Adaptee后,就不可能再去处理
9、Adaptee的子类了。对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。换言之,同一个适配器可以把源类和它的子类都适配到目标接口。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无所谓。* 对于类适配器,适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。对于对象适配器,要重定义Adaptee的行为比较困难,这种情况下,需要定义Adaptee的子类来实现重定义,然后让适配器组合子类。虽然重定义Adaptee的行为比较困难,但是想要增加一些新的行为则方便的很,而且新增加的行为可同时适用于所有的源。*对于类适配器,仅仅引入了一个对象,并不需
10、要额外的引用来间接得到Adaptee。对于对象适配器,需要额外的引用来间接得到Adaptee。建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。.Android ListView中的Adapter模式在开发过程中,ListView的Adapter是我们最为常见的类型之一。一般的用法大致如下: / 适配器public class MyAdapter extends BaseAdapter private LayoutInflater mInflater; List<String> mDatas ; publ
11、ic MyAdapter(Context context, List<String> datas) this.mInflater = LayoutInflater.from(context); mDatas = datas ; Override public int getCount() return mDatas.size(); Override public String getItem(int pos) return mDatas.get(pos); Override public long getItemId(int pos) return pos; / 解析、设置、缓存c
12、onvertView以及相关内容 Override public View getView(int position, View convertView, ViewGroup parent) ViewHolder holder = null; / Item View的复用 if (convertView = null) holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.my_listview_item, null); / 获取title holder.title = (TextView)convertView
13、.findViewById(R.id.title); convertView.setTag(holder); else holder = (ViewHolder)convertView.getTag(); holder.title.setText(mDatas.get(position); return convertView; 这看起来似乎还挺麻烦的,看到这里我们不禁要问,ListView为什么要使用Adapter模式呢? 我们知道,作为最重要的View,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化。那么如何隔离这种变化尤为重要。
14、Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。 通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView函数 ),这样就很好的应对了Item View的可变性。那么List
15、View是如何通过Adapter模式 ( 不止Adapter模式 )来运作的呢 ?我们一起来看一看。 ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一看这个类。public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChang
16、eListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback ListAdapter mAdapter ; / 关联到Window时调用的函数 Override protected void onAttachedToWindow() super.onAttachedToWindow(); / 代码省略 / 给适配器注册一个观察者,该模式下一篇介绍。 if (mAdapter != null && mDataSetObserver = null) mDataSetObserver = new AdapterDataSe
17、tObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); / Data may have changed while we were detached. Refresh. mDataChanged = true; mOldItemCount = mItemCount / 获取Item的数量,调用的是mAdapter的getCount方法 mItemCount = mAdapter.getCount(); mIsAttached = true; /* * 子类需要覆写layoutChildren()函数来布局child vi
18、ew,也就是Item View */ Override protected void onLayout(boolean changed, int l, int t, int r, int b) super.onLayout(changed, l, t, r, b); mInLayout = true; if (changed) int childCount = getChildCount(); for (int i = 0; i < childCount; i+) getChildAt(i).forceLayout(); mRecycler.markChildrenDirty(); if
19、 (mFastScroller != null && mItemCount != mOldItemCount) mFastScroller.onItemCountChanged(mOldItemCount, mItemCount); / 布局Child View layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; / 获取一个Item View View obtainView(int position, boolean isScrap) isScrap
20、0 = false; View scrapView; / 从缓存的Item View中获取,ListView的复用机制就在这里 scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) / 代码省略 child = mAdapter.getView(position, scrapView, this); / 代码省略 else child = mAdapter.getView(position, null, this); / 代码省略 return child; AbsListView定义了
21、集合视图的框架,比如Adapter模式的应用、复用Item View的逻辑、布局Item View的逻辑等。子类只需要覆写特定的方法即可实现集合视图的功能,例如ListView。ListView中的相关方法。 Override protected void layoutChildren() / 代码省略 try super.layoutChildren(); invalidate(); / 代码省略 / 根据布局模式来布局Item View switch (mLayoutMode) case LAYOUT_SET_SELECTION: if (newSel != null) sel = fil
22、lFromSelection(newSel.getTop(), childrenTop, childrenBottom); else sel = fillFromMiddle(childrenTop, childrenBottom); break; case LAYOUT_SYNC: sel = fillSpecific(mSyncPosition, mSpecificTop); break; case LAYOUT_FORCE_BOTTOM: sel = fillUp(mItemCount - 1, childrenBottom); adjustViewsUpOrDown(); break;
23、 case LAYOUT_FORCE_TOP: mFirstPosition = 0; sel = fillFromTop(childrenTop); adjustViewsUpOrDown(); break; case LAYOUT_SPECIFIC: sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop); break; case LAYOUT_MOVE_SELECTION: sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom); b
24、reak; default: / 代码省略 break; / 从上到下填充Item View 只是其中一种填充方式 private View fillDown(int pos, int nextTop) View selectedView = null; int end = (mBottom - mTop); if (mGroupFlags & CLIP_TO_PADDING_MASK) = CLIP_TO_PADDING_MASK) end -= mListPadding.bottom; while (nextTop < end && pos < mIte
25、mCount) / is this the selected item? boolean selected = pos = mSelectedPosition; View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); nextTop = child.getBottom() + mDividerHeight; if (selected) selectedView = child; pos+; return selectedView; / 添加Item View private View makeA
26、ndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) View child; / 代码省略 / Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); / This needs to be positioned and measured setupChild(child, position, y, flow, child
27、renLeft, selected, mIsScrap0); return child; ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。当然这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和A
28、dapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构
29、造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。4.杂谈优点与缺点优点更好的复用性系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。更好的扩展性在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。缺点过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。比如,明明看到调用的是A接口这是理论篇,但是仔细看过我博客的哥们应该知
30、道我其实 Adapter设计模式理论与实践相结合写过很多效果和框架了。这里不做过多的讲解,写着写着看着看着就会了就理解了。2.3.1 我们再也不能直接传字符串数组或是传对象数组过去让自定义View去处理了,所以我们先确定一个自定义的Adapter类,getCount() 和 getView(int position,ViewGroup parent) 先用这两个方法吧后面想到了再说。 /* * Created by Darren on 2016/12/7. * Email: 240336124 * Description: 指示器的适配器 */public abstract class Ind
31、icatorBaseAdapter / 获取总的条数 public abstract int getCount(); / 根据当前的位置获取View public abstract View getView(int position,ViewGroup parent);2.3.2 然后我们来实现指示器的自定义View,TrackIndicatorView 继承自 HorizontalScrollView 。然后我们利用传递过来的Adapter再去动态的添加,我这里就直接上代码吧/* * Created by Darren on 2016/12/13. * Email: 240336124 *
32、Description: ViewPager指示器 */public class TrackIndicatorView extends HorizontalScrollView / 自定义适配器 private IndicatorBaseAdapter mAdapter; / Item的容器因为ScrollView只允许加入一个孩子 private LinearLayout mIndicatorContainer; public TestIndicator(Context context) this(context, null); public TestIndicator(Context co
33、ntext, AttributeSet attrs) this(context, attrs, 0); public TestIndicator(Context context, AttributeSet attrs, int defStyleAttr) super(context, attrs, defStyleAttr); / 初始化Indicator容器用来存放item mIndicatorContainer = new LinearLayout(context); addView(mIndicatorContainer); public void setAdapter(Indicato
34、rBaseAdapter adapter) if (adapter = null) throw new NullPointerException("Adapter cannot be null!"); this.mAdapter = adapter; / 获取Item个数 int count = mAdapter.getCount(); / 动态添加到布局容器 for (int i = 0; i < count; i+) View indicatorView = mAdapter.getView(i, mIndicatorContainer); mIndicatorC
35、ontainer.addView(indicatorView); 效果可想而知,可以写一个Activity测试一下,目前可以动态的添加多个不同样式的布局,如果超出一个屏幕可以左右滑动,我这里就不做演示,待会一起吧。 2.3.3 动态的制定指示器Item的宽度: 目前我们虽然能够动态的去添加各种布局,但是Item的宽度是任意的,我们需要在布局文件中指定一屏显示多少个,如果没有指定那么就获取Item中最宽的一个,如果不够一屏显示就默认显示一屏。我们需要使用自定义属性,这里就不做过多的讲,实在不行大家就自己去看看有关自定义属性的博客或是直接google搜索一下。 / 获取一屏显示多少个Item,默认
36、是0 private int mTabVisibleNums = 0; / 每个Item的宽度 private int mItemWidth = 0; public TrackIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) super(context, attrs, defStyleAttr); / 之前代码省略. / 获取自定义属性值 一屏显示多少个 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TrackInd
37、icatorView); mTabVisibleNums = array.getInt(R.styleable.TrackIndicatorView_tabVisibleNums, mTabVisibleNums); array.recycle(); Override protected void onLayout(boolean changed, int l, int t, int r, int b) super.onLayout(changed, l, t, r, b); if (changed) / 指定Item的宽度 mItemWidth = getItemWidth(); int i
38、temCounts = mAdapter.getCount(); for (int i = 0; i < itemCounts; i+) / 指定每个Item的宽度 mIndicatorContainer.getChildAt(i).getLayoutParams().width = mItemWidth; Log.e(TAG, "mItemWidth -> " + mItemWidth); /* * 获取每一个条目的宽度 */ public int getItemWidth() int itemWidth = 0; / 获取当前控件的宽度 int width
39、= getWidth(); if (mTabVisibleNums != 0) / 在布局文件中指定一屏幕显示多少个 itemWidth = width / mTabVisibleNums; return itemWidth; / 如果没有指定获取最宽的一个作为ItemWidth int maxItemWidth = 0; int mItemCounts = mAdapter.getCount(); / 总的宽度 int allWidth = 0; for (int i = 0; i < mItemCounts; i+) View itemView = mIndicatorContain
40、er.getChildAt(i); int childWidth = itemView.getMeasuredWidth(); maxItemWidth = Math.max(maxItemWidth, childWidth); allWidth += childWidth; itemWidth = maxItemWidth; / 如果不足一个屏那么宽度就为 width/mItemCounts if (allWidth < width) itemWidth = width / mItemCounts; return itemWidth; 目前我们各种情况都测试了一下,一种是直接在布局文件
41、中指定一屏可见显示4个,一种是不指定就默认以最大的Item的宽度为准,最后一种就是不指定又不足一个屏幕默认就显示一屏。看一下效果吧2.4结合ViewPager 接下来我们就需要结合ViewPager了,也就需要实现一系列重要的效果: 2.4.1. 当ViewPager滚动的时候头部需要自动将当前Item滚动到最中心; 2.4.2. 点击Item之后ViewPager能够切换到对应的页面; 2.4.3. 需要页面切换之后需要回调,让用户切换当前选中的状态,需要在Adapter中增加方法; 2.4.4. 有些效果需要加入指示器,但并不是每种效果都需要2.4.1. 当ViewPager滚动的时候头部
42、自动将当前Item滚动到最中心 我们目前不光需要Adapter,还需要一个参数就是ViewPager,需要监听ViewPager的滚动事件 /* * 重载一个setAdapter的方法 * param adapter 适配器 * param viewPager 联动的ViewPager */ public void setAdapter(IndicatorBaseAdapter adapter, ViewPager viewPager) / 直接调用重载方法 setAdapter(adapter); / 为ViewPager添加滚动监听事件 this.mViewPager = viewPage
43、r; mViewPager.addOnPageChangeListener(this); Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) / 在ViewPager滚动的时候会不断的调用该方法 Log.e(TAG,"position -> "+position+" positionOffset -> "+positionOffset); / 在不断滚动的时候让头部的当前Item一直保持在最中心 in
44、dicatorScrollTo(position,positionOffset); /* * 不断的滚动头部 */ private void indicatorScrollTo(int position, float positionOffset) / 当前的偏移量 int currentOffset = (int) (position + positionOffset) * mItemWidth); / 原始的左边的偏移量 int originLeftOffset = (getWidth()-mItemWidth)/2; / 当前应该滚动的位置 int scrollToOffset = cu
45、rrentOffset - originLeftOffset; / 调用ScrollView的scrollTo方法 scrollTo(scrollToOffset,0); 2.4.2. 点击Item之后ViewPager能够切换到对应的页面 public void setAdapter(IndicatorBaseAdapter adapter) if (er = null) throw new NullPointerException("Adapter cannot be null!"); this.mAdapter = adapter; / 获取Item个数 int co
46、unt = mAdapter.getCount(); / 动态添加到布局容器 for (int i = 0; i < count; i+) View indicatorView = mAdapter.getView(i, mIndicatorContainer); mIndicatorContainer.addView(indicatorView); switchIndicatorClick(indicatorView,i); /* * Indicator条目点击对应切换ViewPager */ private void switchIndicatorClick(View indicatorView, final int position) indicatorView.setOnClickListener(new OnClickListener() Override public void onClic
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 江西中医药高等专科学校《智能机器人技术》2023-2024学年第二学期期末试卷
- 重庆电讯职业学院《阿拉伯语语法》2023-2024学年第二学期期末试卷
- 北京交通职业技术学院《蜂窝移动通信》2023-2024学年第二学期期末试卷
- 达州职业技术学院《公共与市场的边界》2023-2024学年第二学期期末试卷
- 成都理工大学工程技术学院《英美文学(3)》2023-2024学年第二学期期末试卷
- 柳州工学院《录音与编辑技术》2023-2024学年第二学期期末试卷
- 湖南人文科技学院《操作系统结构分析》2023-2024学年第二学期期末试卷
- 山西能源学院《时装表演艺术4》2023-2024学年第二学期期末试卷
- 变电站冬季安全施工方案
- 2025合作共识协议合同标准版本
- 火灾自动报警系统设计规范完整版2025年
- 德庆县2024-2025学年三年级数学第二学期期末统考模拟试题含解析
- 制造业产品全生命周期管理流程
- 安全意识教育试题及答案
- SZDBZ 171-2016 物业服务人员管理规范
- 《食品营养与健康》课件
- 屋面保温工程施工方案
- 课题申报书:大学中学融通视域下拔尖创新人才早期培养评价标准体系构建的实证研究
- 复旦大学-自主招生个人陈述自荐信标准范文
- 《东北风情课件》课件
- 土木工程专业就业能力展示
评论
0/150
提交评论