自己造轮子——一个可爱的ViewPager指示器

在很久以前,我写了一个android广告栏控件,https://github.com/dongjunkun/BannerLayout,其实当时只是学习之用,自己也不是很懂,一边写代码一边查资料及其他开源控件,勉勉强强的算是完成了,现在github上也有了几百个star,时至今日,我也一直在用,中间也没太多的精力把这个库完善,因为本来就是主打简单实用,所以功能不多,拓展性也一般,根据网友的要求,中间将图片加载模块提取出来了,但指示器封装到了BannerLayout内部了,前段时间我们的设计出了一款圆角矩形的指示器,而且带有阴影效果,之前的方法就不行了,趁着有点空闲的时间,又单独写了个指示器控件,如下:

indicator.gif

github传送门:CuteIndicator

实现思路

  • 通过GradientDrawable实现,每一个点都是一个Drawable,设置形状,颜色,大小,圆角等等,但发现有个大坑,根本不能设置阴影,BannerLayout采用的方案就是这个,就只好放弃。
  • 通过XML绘制,结果一样,发现不过一个是通过代码,而这边是通过xml属性设置,本质并无区别,放弃。
  • 通过 重新onDraw方法绘制指示器,好多table指示器都是通过这种方法实现的,可以尝试下,而且paint包含有setShadowLayer方法可绘制阴影。

Step by Step

1、先新建一个自定义View继承于LinearLayout,重写构造函数,重写OnDraw方法,如下:
class CuteIndicator : LinearLayout {

    constructor(context: Context) : super(context) {
        init(null, 0)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init(attrs, 0)
    }

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
        init(attrs, defStyle)
    }

    private fun init(attrs: AttributeSet?, defStyle: Int) {

        val a = context.obtainStyledAttributes(
                attrs, R.styleable.CuteIndicator, defStyle, 0)
       //自定义属性赋值  
        a.recycle()
    }


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
    }

也可以利用android Studio创建自定义View模板。

2、重写onDraw随便画点东西
  override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
          rectf.left = getLeft()
          rectf.top = getTop()
          rectf.right = getRight()
          rectf.bottom = getBottom()
         canvas.drawRect(rectf,paint)
    }

这个地方有点小坑,试了一会儿,onDraw方法都没有触发,继承View会默认触发,但继承LinearLayout不会,依靠万能的搜索引擎,发现了几行有用的代码

fun init(){
        ...
        //允许Draw
        setWillNotDraw(false)
        ...
}
//触发onDraw方法,至于两个方法区别,自行查阅
invalidate()
postInvalidate()

终于在控制台检测到了onDraw方法被执行,但画面依旧是一片空白,什么也没有画上去,检查了一下,所有方法都执行了,每个值都能打印出来,问题出在位置不对,getLeft(),getRight(),getTop(),getButtom()获取的是控件在界面的绝对位置,而每个控件都有单独的坐标体系,左上角(0,0)开始,只需做如下修改

  rectf.left = 0
  rectf.top = 0
  rectf.right = getWidth()
  rectf.bottom = geHeight()
3、开始绘制一个不含动画效果的指示器(先易后难)
indicator_jump.gif

先捋一下需要确定哪些东西,在最开始我已经展示了效果图,选中的是一个圆角矩形,未选中的是一个圆形,selectedWidth(选中的指示器宽度),dia(圆形的直径),space(任意两个指示器直径的间距),假设默认选中第一个,没有动画的指示器仅仅在广告栏滑动过去后才进行切换

override fun onPageSelected(position: Int) {
                super.onPageSelected(position)                
                firstVisiblePosition = position
                invalidate()     
            }

只需要在onPageSelected方法中监听页面是否已经切换,调用 invalidate() 绘制就可以了

4、绘制阴影

之前在说实现思路上没有采用XML或者GradientDrawable方式,其原因就是无法绘制阴影,因为android没有提供相应的api,关键代码,两行代码缺一不可,都加上才有效果

setLayerType(View.LAYER_TYPE_SOFTWARE, null)
setShadowLayer(shadowRadius, shadowRadius / 2, shadowRadius / 2, shadowColor.toInt())

效果预览,为了看效果,弄个夸张一点的黑色,具体阴影颜色可通过自定义属性进行设置

indicator_shadow.gif
5、开始绘制根据页面滑动,指示器便会相应反应的指示器,跟手动画,也是最终效果,至于onDraw里面的算法,我不准备多讲,代码在github上

ViewPager的onPagerChangeListener有三个方法,上面已经重写了一个,如果想要指示器跟随着页面进行变化,便要用到这个方法进行监听

override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels)
                firstVisiblePosition = position
                lastPositionOffset = positionOffset
                invalidate()
            }
  • position是ViewPager第一个可见的页面位置
  • positionOffset滑动的距离比例[0,1]
  • positionOffsetPixels实际滑动的距离

最开始的思路是想要监听页面是向左还是向右滑动,然后再去计算,然后发现完全没必要。

github传送门:CuteIndicator

代码量很少,总共也就100多行,核心代码也就几十行,重在理解。

Todo

1、暂未支持循环的ViewPager

如果觉得我的文章对你有帮助,留下一点痕迹呗,star一下就是对我最好的鼓励
如需转载,请自便,留下署名和原文链接即可

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,062评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,711评论 2 59
  • 不劳动的劳动节 鸟一样窝在家里 随便吃点儿然后睡到昏天地暗 然后醒来 然后追忆似水流年 信马由缰 落花...
    风柯月渚阅读 608评论 0 0
  • 小时候,总觉得外面的世界很大很美,想飞得越远越好。而现在每次午夜梦回,想起却是家乡那一条条回家的小路,有从爸爸单位...
    D琼芳阅读 334评论 2 2
  • 自画像 文/唐宋 白云如此低垂要睡到我的肩膀上 可是我却琢磨不透云的心事 白皙是云的点缀 从青春飘向古老的历史上 ...
    唐宋a阅读 472评论 21 13