Android 实现带指示器的自动轮播式ViewPager
来源:互联网 发布:护肤品真假查询软件 编辑:程序博客网 时间:2024/05/16 19:10
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
- 前言
- 效果展示
- GitHub地址及使用介绍
- 原理简析
- 实现自动滚动
- 实现指示器
- 实现Page的点击事件处理
前言
最近在做项目的时候,有个需求就是实现自动轮播式的ViewPager,最直观的例子就是知乎日报顶部的ViewPager,它内部有着好几个子view,每个一段时间便自动滑动到下一个item view,而底部的指示器也随之跟着改变。使用这种ViewPager的好处是在有限的空间内可以展示出多样化的信息。轮播式ViewPager广泛应用于各种应用内部,用于展示广告等。抱着学习和分享的目的,笔者把轮播式ViewPager写成了一个独立的控件,以方便以后的使用。
效果展示
话不多说,我们先来看看实现的效果是怎样的:
从上面的动态图可以看到,当我们手指拖动ViewPager的时候,下方的指示器随着页面的滑动而滑动,当点击添加数据的按钮的时候,ViewPager的数据项变多,同时下方的指示器也随之改变,适应了数据项的数目。
从上面的动态图可以看到,当我们不用手指进行拖动的时候,该ViewPager会每隔4s左右的时间自动进行滚动,滚动到最后一个item view的时候,下一次会滚到第一个位置。
GitHub地址及使用介绍
读者可以直接到我的GitHub中获取源码。
GitHub:BannerViewPager,控件及其相关文件都放在了该目录下的library模块内,而app模块则是上面效果展示的一个简单应用。
通过以下几个步骤,就能方便地使用该控件了:
1、像普通的ViewPager一样,在布局文件中放入该控件如下:
<code class="language-xml hljs has-numbering"><span class="hljs-tag"><<span class="hljs-title">LinearLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span> <span class="hljs-attribute">xmlns:tools</span>=<span class="hljs-value">"http://schemas.android.com/tools"</span> <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"vertical"</span>></span> <span class="hljs-tag"><<span class="hljs-title">com.chenyu.library.bannerViewPager.BannerViewPager</span> <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/banner"</span> <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"200dp"</span>></span> <span class="hljs-tag"></<span class="hljs-title">com.chenyu.library.bannerViewPager.BannerViewPager</span>></span> <span class="hljs-comment"><!-- others --></span><span class="hljs-tag"></<span class="hljs-title">LinearLayout</span>></span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul>
2、获取BannerViewPager的实例,进行相应的配置,比如我们使用ViewPager的时候,也需要设置它的适配器等。这里笔者实现了一个ViewPagerAdapter,用作BannerViewPager的适配器:
<code class="language-java hljs has-numbering"><span class="hljs-comment">//获取BannerViewPager实例</span>bannerViewPager = (BannerViewPager) findViewById(R.id.banner);<span class="hljs-comment">//实例化ViewPagerAdapter,第一个参数是View集合,第二个参数是页面点击监听器</span>mAdapter = <span class="hljs-keyword">new</span> ViewPagerAdapter(mViews, <span class="hljs-keyword">new</span> OnPageClickListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageClick</span>(View view, <span class="hljs-keyword">int</span> position) { Log.d(<span class="hljs-string">"cylog"</span>,<span class="hljs-string">"position:"</span>+position); }});<span class="hljs-comment">//设置适配器</span>bannerViewPager.setAdapter(mAdapter);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li></ul>
和一般的ViewPager没什么两样,都是:获取实例——创建适配器——设置适配器。而适配器的数据集一般都是一个View集合,用作ViewPager的item view,所以需要事先准备好相应的View集合。此外,一般轮播式ViewPager点击某一项后会打开相应的页面,所以这里提供了一个OnPageClickListener的监听器,在创建适配器的时候同时创建该监听器即可。
原理简析
接下来,笔者将简要分析BannerViewPager的实现思路,具体的请读者参考源码~
实现自动滚动
首先,我们先思考一下,系统自带的ViewPager是一个独立控件,没有指示器,也没有自动滚动的功能,但是它是一个现成的,可左右滑动的控件,我们肯定是需要ViewPager的,因此,我们可以利用一个布局,把ViewPager包裹起来,同时在这个布局里面再放入indicator(指示器)。
那么,第一步,先新建BannerViewPager.Java继承自FrameLayout,而这个FrameLayout有两个子元素:ViewPager和indicator。至于indicator,下面会说到。在构造函数内对这两个控件进行初始化先:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BannerViewPager</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FrameLayout</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ViewPager</span>.<span class="hljs-title">OnPageChangeListener</span> {</span> <span class="hljs-keyword">private</span> ViewPager mViewPager; <span class="hljs-keyword">private</span> ViewPagerIndicator mIndicator; <span class="hljs-keyword">private</span> ViewPagerAdapter mAdapter; <span class="hljs-comment">//...</span> <span class="hljs-keyword">public</span> <span class="hljs-title">BannerViewPager</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr) { <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr); <span class="hljs-keyword">this</span>.mContext = context; initViews(); } <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initViews</span>() { <span class="hljs-comment">//initialize the viewpager</span> mViewPager = <span class="hljs-keyword">new</span> ViewPager(mContext); ViewPager.LayoutParams lp = <span class="hljs-keyword">new</span> ViewPager.LayoutParams(); lp.width = ViewPager.LayoutParams.MATCH_PARENT; lp.height = ViewPager.LayoutParams.MATCH_PARENT; mViewPager.setLayoutParams(lp); <span class="hljs-comment">//initialize the indicator</span> mIndicator = <span class="hljs-keyword">new</span> ViewPagerIndicator(mContext); FrameLayout.LayoutParams indicatorlp = <span class="hljs-keyword">new</span> FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); indicatorlp.gravity = Gravity.BOTTOM | Gravity.CENTER; indicatorlp.bottomMargin = <span class="hljs-number">20</span>; mIndicator.setLayoutParams(indicatorlp); } <span class="hljs-comment">//省略...</span>}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li></ul>
这里没什么好说的,主要是对ViewPager和ViewPagerIndicator进行初始化,设置它们的布局参数以便在FrameLayout中得到正确的显示。
接着,对ViewPager实现自动滚动,这个的实现原理也不难,我们只要知道每时每刻的ViewPager的滑动状态、当前的page position值即可,而ViewPager有这样一个监听器:ViewPager.OnPageChangeListener,只要ViewPager进行了滑动,就会回调这个监听器的如下几个方法:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">OnPageChangeListener</span> {</span> <span class="hljs-comment">//只要ViewPager进行了滑动,该方法就会回调</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageScrolled</span>(<span class="hljs-keyword">int</span> position, <span class="hljs-keyword">float</span> positionOffset, <span class="hljs-keyword">int</span> positionOffsetPixels); <span class="hljs-comment">//当前页面被选定的时候,回调</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageSelected</span>(<span class="hljs-keyword">int</span> position); <span class="hljs-comment">//ViewPager的状态发生改变的时候,回调</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageScrollStateChanged</span>(<span class="hljs-keyword">int</span> state);}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>
那么,我们为ViewPager设置监听器(调用addOnPageChangeListener方法),并且重写这几个方法以实现我们的需求:
<code class="language-java hljs has-numbering"> <span class="hljs-comment">//保存当前的position值</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mCurrentPosition; <span class="hljs-comment">//viewpager's rolling state</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> mViewPagerScrollState; <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageScrolled</span>(<span class="hljs-keyword">int</span> position, <span class="hljs-keyword">float</span> positionOffset, <span class="hljs-keyword">int</span> positionOffsetPixels) { setIndicator(position,positionOffset); <span class="hljs-comment">//下面会讲到</span> } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageSelected</span>(<span class="hljs-keyword">int</span> position) { mCurrentPosition = position; } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onPageScrollStateChanged</span>(<span class="hljs-keyword">int</span> state) { <span class="hljs-keyword">if</span>(state == ViewPager.SCROLL_STATE_DRAGGING){ mViewPagerScrollState = ViewPager.SCROLL_STATE_DRAGGING; }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(state == ViewPager.SCROLL_STATE_IDLE){ mReleasingTime = (<span class="hljs-keyword">int</span>) System.currentTimeMillis(); mViewPagerScrollState = ViewPager.SCROLL_STATE_IDLE; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li></ul>
每当当前页面被选中的时候,就会调用onPageSelected方法,此时保存当前position值。那么,什么叫做当前页面被选中呢?经过实验验证,当一个Item被完全展示在ViewPager中的时候,就是选中状态,但如果当前正在被手指拖动,即使下一个item滑动到了中间位置,也不是选中状态。接着,我们看onPageScrollStateChanged方法,当ViewPager的状态发生改变的时候,就会触发。那么,**ViewPager的状态改变是什么意思呢?**ViewPager有如下三种状态:IDLE,停止状态,无手指触摸;DRAGGING,正在被手指拖动;SETTLING,松开手指的时候,ViewPager由于惯性向能滑到的最后一个位置滑去的状态。我们重写的方法中,mViewPageSrollState记录了ViewPager的实时状态,同时停止状态的时候,也记录了一个mReleasingTime值,这个值的作用下面会介绍。通过这个监听器,我们获取到了mCurrentPosition和mViewPageScrollState这两个值。
接下来,我们要考虑自动任务的问题了。在Android中,自动任务可以使用Handler和Runnable来实现,通过postDelay方法来不断实现循环,代码如下:
<code class="language-java hljs has-numbering"> <span class="hljs-keyword">private</span> Handler mHandler = <span class="hljs-keyword">new</span> Handler(){ <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span>(Message msg) { <span class="hljs-keyword">switch</span> (msg.what){ <span class="hljs-keyword">case</span> MESSAGE_AUTO_ROLLING: <span class="hljs-keyword">if</span>(mCurrentPosition == mAdapter.getCount() - <span class="hljs-number">1</span>){ mViewPager.setCurrentItem(<span class="hljs-number">0</span>,<span class="hljs-keyword">true</span>); }<span class="hljs-keyword">else</span> { mViewPager.setCurrentItem(mCurrentPosition + <span class="hljs-number">1</span>,<span class="hljs-keyword">true</span>); } postDelayed(mAutoRollingTask,mAutoRollingTime); <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> MESSAGE_AUTO_ROLLING_CANCEL: postDelayed(mAutoRollingTask,mAutoRollingTime); <span class="hljs-keyword">break</span>; } } }; <span class="hljs-javadoc">/** * This runnable decides the viewpager should roll to next page or wait. */</span> <span class="hljs-keyword">private</span> Runnable mAutoRollingTask = <span class="hljs-keyword">new</span> Runnable() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() { <span class="hljs-keyword">int</span> now = (<span class="hljs-keyword">int</span>) System.currentTimeMillis(); <span class="hljs-keyword">int</span> timediff = mAutoRollingTime; <span class="hljs-keyword">if</span>(mReleasingTime != <span class="hljs-number">0</span>){ timediff = now - mReleasingTime; } <span class="hljs-keyword">if</span>(mViewPagerScrollState == ViewPager.SCROLL_STATE_IDLE){ <span class="hljs-comment">//if user's finger just left the screen,we should wait for a while.</span> <span class="hljs-keyword">if</span>(timediff >= mAutoRollingTime * <span class="hljs-number">0.8</span>){ mHandler.sendEmptyMessage(MESSAGE_AUTO_ROLLING); }<span class="hljs-keyword">else</span> { mHandler.sendEmptyMessage(MESSAGE_AUTO_ROLLING_CANCEL); } }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(mViewPagerScrollState == ViewPager.SCROLL_STATE_DRAGGING){ mHandler.sendEmptyMessage(MESSAGE_AUTO_ROLLING_CANCEL); } } };</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li></ul>
在mAutoRollingTask这个Runnable内,我们根据不同的mViewPagerScrollState来决定是让ViewPager滚动到下一个page还是等待,因为如果用户当前正在触摸ViewPage,那么肯定是不能自动滚动到下一页的,此外,还有一种情况,就是当用户手指离开屏幕的时候,需要等待一段时间才能开始自动滚动任务,否则会造成不好的用户体验,这也就是mReleasingTime的作用之处了。在Handler中,根据Runnable发送过来的不同信息来进行不同的操作,如果需要滚动到下一个页面,则调用ViewPager#setCurrentItem方法来进行滑动,该方法有两个参数,第一个参数是要滑动的位置,第二个参数表示是否开启动画。
实现指示器
接下来,我们来考虑,指示器怎么实现。指示器有如下需求:指示器由一系列圆点构成,未被选中的Page所对应的圆点为灰色,而选中的Page所对应的圆点为橙色,橙色的圆点能随着Page的滑动而滑动。当ViewPage的数据变动的时候,比如新增了页面,那么指示器所包含的圆点也会随着变多。
那么,我们可以这样来实现需求:灰色的圆点作为Indicator的背景,通过onDraw()方法来绘制,而橙色圆点则通过一个子View来显示,利用onLayout()方法来控制它的位置,这样就能实现橙色圆点在灰色圆点上运动的效果了。而它们具体的位置控制,可以利用上面ViewPager.OnPageChangeListener#onPageScrolled方法来获取具体的位置以及位置偏移百分比。
我们先来实现绘制部分,新建ViewPagerIndicator.java继承自LinearLayout,先对属性初始化:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewPagerIndicator</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LinearLayout</span> {</span> <span class="hljs-keyword">private</span> Context mContext; <span class="hljs-keyword">private</span> Paint mPaint; <span class="hljs-keyword">private</span> View mMoveView; <span class="hljs-comment">//省略...</span> <span class="hljs-keyword">public</span> <span class="hljs-title">ViewPagerIndicator</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyleAttr) { <span class="hljs-keyword">super</span>(context, attrs, defStyleAttr); <span class="hljs-keyword">this</span>.mContext = context; init(); } <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span>() { <span class="hljs-comment">//setOrientation(LinearLayout.HORIZONTAL);</span> setWillNotDraw(<span class="hljs-keyword">false</span>); mPaint = <span class="hljs-keyword">new</span> Paint(); mPaint.setAntiAlias(<span class="hljs-keyword">true</span>); mPaint.setColor(Color.GRAY); mMoveView = <span class="hljs-keyword">new</span> MoveView(mContext); addView(mMoveView); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) { <span class="hljs-keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mPadding + (mRadius*<span class="hljs-number">2</span> + mPadding) * mItemCount,<span class="hljs-number">2</span>*mRadius + <span class="hljs-number">2</span>*mPadding); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onDraw</span>(Canvas canvas) { <span class="hljs-keyword">super</span>.onDraw(canvas); <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>;i < mItemCount;i++){ canvas.drawCircle(mRadius + mPadding + mRadius * i *<span class="hljs-number">2</span> + mPadding * i, mRadius + mPadding,mRadius,mPaint); } } <span class="hljs-comment">//省略...</span> <span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MoveView</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">View</span> {</span> <span class="hljs-keyword">private</span> Paint mPaint; <span class="hljs-keyword">public</span> <span class="hljs-title">MoveView</span>(Context context) { <span class="hljs-keyword">super</span>(context); mPaint = <span class="hljs-keyword">new</span> Paint(); mPaint.setAntiAlias(<span class="hljs-keyword">true</span>); mPaint.setColor(Color.argb(<span class="hljs-number">255</span>,<span class="hljs-number">255</span>,<span class="hljs-number">176</span>,<span class="hljs-number">93</span>)); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) { <span class="hljs-keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mRadius*<span class="hljs-number">2</span>,mRadius*<span class="hljs-number">2</span>); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onDraw</span>(Canvas canvas) { <span class="hljs-keyword">super</span>.onDraw(canvas); canvas.drawCircle(mRadius,mRadius,mRadius,mPaint); } }}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li></ul>
从上面的代码可以看到,在init()方法内,我们调用了setWillNotDraw(false)方法,这个方法有什么用呢?如果有写过自定义View的读者应该知道,ViewGroup默认是不会调用它自身的onDraw()方法的,只有调用了该方法设置为false或者给ViewGroup设置一种背景颜色的情况下才会调用onDraw()方法。
解决了这个问题后,我们来看onMeasure()方法,在这个方法内,我们要对该indicator的宽高做出测量,以便接下来的布局和绘制流程,而对于我们的需求而言,只要该布局能够包裹住我们的指示器,并且四边留有一定的空间即可,那么布局的宽度就与Page的数量有关了。为了方便起见,这里先给一个默认值,比如5个Page,那么对应5个灰色的圆点。
我们接着看onDraw()方法,这个方法内部,根据mItemCount的数量,来进行绘制圆形,这里没什么好讲的,只要注意他们之间的距离就可以了。
接着,我们来绘制橙色的圆点,新建一个内部类,继承自View,同样通过onMeasure、onDraw方法来进行测量、绘制流程,只不过颜色变了而已。
好了,绘制部分就完成了,接下来就是让这个MoveView进行移动了,由于要使MoveView配合Page的滑动而滑动,我们需要Page的具体位置以及位置偏移量,而这两个数值是在BannerViewPager的内部中获得的,所以我们可以在BannerViewPager中,每一次调用onPageScrolled方法的时候,来调用我们的ViewPagerIndicator的一个方法,而在这个方法内部,来请求布局,这样就能实现MoveView随着Page的滑动而滑动的效果了,具体如下:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewPagerIndicator</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">LinearLayout</span> {</span> <span class="hljs-comment">//以上省略..</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setPositionAndOffset</span>(<span class="hljs-keyword">int</span> position,<span class="hljs-keyword">float</span> offset){ <span class="hljs-keyword">this</span>.mCurrentPosition = position; <span class="hljs-keyword">this</span>.mPositionOffset =offset; requestLayout(); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onLayout</span>(<span class="hljs-keyword">boolean</span> changed, <span class="hljs-keyword">int</span> l, <span class="hljs-keyword">int</span> t, <span class="hljs-keyword">int</span> r, <span class="hljs-keyword">int</span> b) { <span class="hljs-keyword">super</span>.onLayout(changed, l, t, r, b); mMoveView.layout( (<span class="hljs-keyword">int</span>) (mPadding + mDistanceBtwItem * (mCurrentPosition + mPositionOffset) ), mPadding, (<span class="hljs-keyword">int</span>) (mDistanceBtwItem * ( <span class="hljs-number">1</span> + mCurrentPosition + mPositionOffset) ), mPadding+mRadius*<span class="hljs-number">2</span>); }}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul>
在setPositionAndOffset方法内调用了requestLayout()方法,这个方法会导致View树的测量、布局、重绘流程的发生,因此在onLayout方法内,通过mCurrentPosition、mPositionOffset这两个值来控制MoveView的位置就可以了。
好了,到现在为止,ViewPagerIndicator基本已经完成了,但是还有一个问题,如果适配器里面的数据刷新了,page的数量变多了,而指示器的数目却依然没变,上面我们使用的mItemCount是默认值,为5个。因此,我们必须在数据刷新的时候,及时通知Indicator来增加指示器的数目。但是,我们进一步想想,数据列表保存在Adapter中,如果ViewPagerIndicator想要获取数据,那就要得到Adapter的一个引用,或者说Adapter需要得到ViewPagerIndicator的引用以便能够通知它,如果这样做的话,相当于把两个相关性不大的类联系到了一起,耦合度过高,这样不利于以后的维护。
因此,这里笔者采用了观察者模式来实现Adapter数据刷新时通知ViewPagerIndicator的这样一个需求。先新建两个接口,一个是DataSetSubscriber,观察者;另一个是DataSetSubject,被观察者。
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">DataSetSubscriber</span> {</span> <span class="hljs-keyword">void</span> update(<span class="hljs-keyword">int</span> count);}<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">DataSetSubject</span> {</span> <span class="hljs-keyword">void</span> registerSubscriber(DataSetSubscriber subscriber); <span class="hljs-keyword">void</span> removeSubscriber(DataSetSubscriber subscriber); <span class="hljs-keyword">void</span> notifySubscriber();}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul>
这里实现思路是这样的:在BannerViewPager内实现一个DataSetSubscriber(观察者),在ViewPageAdapter内实现DataSetSubject(被观察者),通过registerSubscriber方法进行注册,当ViewPageAdapter的数据列表发生变动的时候,回调DataSetSubscriber的update()方法,并把当前的数据长度作为参数传递进来,而BannerViewPager再进一步调用ViewPagerIndicator的方法来重新布局即可。
先来看ViewPagerIndicator.java:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ViewPagerAdapter</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">PagerAdapter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DataSetSubject</span> {</span> <span class="hljs-keyword">private</span> List<DataSetSubscriber> mSubscribers = <span class="hljs-keyword">new</span> ArrayList<>(); <span class="hljs-keyword">private</span> List<? extends View> mDataViews; <span class="hljs-keyword">private</span> OnPageClickListener mOnPageClickListener; <span class="hljs-javadoc">/** * 构造函数 *<span class="hljs-javadoctag"> @param</span> mDataViews view列表 */</span> <span class="hljs-keyword">public</span> <span class="hljs-title">ViewPagerAdapter</span>(List<? extends View> mDataViews,OnPageClickListener listener) { <span class="hljs-keyword">this</span>.mDataViews = mDataViews; <span class="hljs-keyword">this</span>.mOnPageClickListener = listener; } <span class="hljs-comment">//省略...</span> <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">notifyDataSetChanged</span>() { <span class="hljs-keyword">super</span>.notifyDataSetChanged(); notifySubscriber(); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">registerSubscriber</span>(DataSetSubscriber subscriber) { mSubscribers.add(subscriber); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">removeSubscriber</span>(DataSetSubscriber subscriber) { mSubscribers.remove(subscriber); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">notifySubscriber</span>() { <span class="hljs-keyword">for</span>(DataSetSubscriber subscriber : mSubscribers){ subscriber.update(getCount()); } }}```由于数据列表的变动一般都会调用notifyDataSetChanged()方法,所以我们在这个方法内再调用notifySubscriber()方法即可。而在BannerViewPager,则实现DataSetSubscriber的update()方法即可,如下所示:```java<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setAdapter</span>(ViewPagerAdapter adapter){ mViewPager.setAdapter(adapter); mViewPager.addOnPageChangeListener(<span class="hljs-keyword">this</span>); mAdapter = adapter; mAdapter.registerSubscriber(<span class="hljs-keyword">new</span> DataSetSubscriber() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">update</span>(<span class="hljs-keyword">int</span> count) { mIndicator.setItemCount(count); } }); <span class="hljs-comment">//add the viewpager and the indicator to the container.</span> addView(mViewPager); addView(mIndicator); <span class="hljs-comment">//start the auto-rolling task if needed</span> <span class="hljs-keyword">if</span>(isAutoRolling){ postDelayed(mAutoRollingTask,mAutoRollingTime); }}<div class=<span class="hljs-string">"se-preview-section-delimiter"</span>></div></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li></ul>
在update()方法内,调用了ViewPagerIndicator#setItemCount方法,从而重新布局。
那么,指示器也实现完毕了。
实现Page的点击事件处理
还有最后一个需求,就是对Page的点击进行处理,因为往往ViewPager的内容只是一个概括性的内容,为了得到更加详细的信息,用户通常会点击它的item从而打开一个新的页面,这样就需要我们对点击事件进行处理了。其实实现方式不难,思路类似于笔者之前在RecyclerView的相关文章的处理点击事件中的方式,通过定义一个新的接口:OnPageClickListener,定义一个onPageClick方法。如下:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">OnPageClickListener</span> {</span> <span class="hljs-keyword">void</span> onPageClick(View view,<span class="hljs-keyword">int</span> position);}<div class=<span class="hljs-string">"se-preview-section-delimiter"</span>></div></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>
只要在item view初始化的时候,给每个item view都设置一个View.OnClickListener,并且在onClick方法里面调用我们的onPageClick方法即可。
具体如下所示,ViewPagerAdapter:
<code class="language-java hljs has-numbering"><span class="hljs-annotation">@Override</span><span class="hljs-keyword">public</span> View <span class="hljs-title">instantiateItem</span>(ViewGroup container, <span class="hljs-keyword">int</span> position) { View view = mDataViews.get(position); <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> i = position; <span class="hljs-keyword">if</span>(mOnPageClickListener != <span class="hljs-keyword">null</span>){ view.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span>(View v) { mOnPageClickListener.onPageClick(v,i); } }); } container.addView(view); <span class="hljs-keyword">return</span> view;}</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>
在构建适配器的时候,同时实现OnPageClickListener即可。
以上便是本文的全部内容,非常感谢你的阅读~
欢迎到GitHub中获取本文的源码,欢迎star or fork。再次感谢!
- Android 实现带指示器的自动轮播式ViewPager
- Android 实现带指示器的自动轮播式ViewPager
- 带指示器的自动轮播式ViewPager
- Android-自动轮播的ViewPager+滑动指示器
- Android实现ViewPager自动轮播
- 自动轮播的viewPager实现
- Android 自动轮播的Viewpager
- android之自动轮播的ViewPager
- VIewPager实现自动轮播
- 自动轮播的viewpager
- ViewPager的自动轮播
- 自动轮播的Viewpager
- ViewPager 带小圆点 自动轮播
- ViewPager 带小圆点 自动轮播
- Android 仿网易新闻 ViewPager 实现图片自动轮播
- ViewPager + Handler 实现的图片自动轮播
- ViewPager实现图片的自动轮播和无限循环
- ViewPager+Handler实现图片自动轮播的效果
- 分布式事物处理方式要点
- DBCA创建数据库实例
- *[Lintcode] Heapify 堆化
- 众数问题
- TRIGGER的上升和下降的检测简单的仿真
- Android 实现带指示器的自动轮播式ViewPager
- 数字货币算法
- Android多渠道打包(二):友盟多渠道打包
- 解惑 spring 嵌套事务
- mybatis association 映射:同文件中的使用
- 使用shell下载第三方网站的js和css文件
- No exception of type HibernateException can be thrown; an exception type must be a subclass of Thro
- MySQL 性能优化的最佳20多条经验分享
- Eclipse 中找不到 LocalBroadcastManager类