老外的自定义View面试题实现篇

欠大家一篇文章,这段时间事情较多常在外面跑来跑去的,其本上没什么时间静下来写代码。然后看到了不少的网友给我反馈,想看一下是如何实现这个效果的:

原面试题传送门

我看到有人通过RecyclerView来实现这个效果,其实也可以,只是背离了考查自定义UI开发的目的。这里我做了一个简单的实现,当然是不完整的,我希望大家自己动手来完善它,那样这个实例中涉及的知识才能真正转化成你自己的技能。

这里简单说一下实现步骤:

  1. 实现自定义的圆角带阴影的View
  1. 实现GroupView+Adapter完成布局
  2. 实现滑动

源码下载地址:Github

实现圆角带阴影的ImaegView

之前我们也有提过有几种实现图片圆角的方式,这里我们只展示一下使用BitmapShader的方式。我们使用paint.setShader设置画笔的内容,再用drawCircle通过设定好的paint画圆即可实现图片的圆角效果。在真正偿试时,你可能需要先解决如下的问题:

  1. canvas的坐标系和drawCircle的坐标关系。
public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
  1. 了解Shader和BitmapShader。
new BitmapShader(imageBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

BitmapShader的三种TileMode: CLAMP, REPEAT, MIRROR

  1. 了解一些Matrix的基础知识。
    学会让Matrix进行缩放让图片居中显示。

  2. 了解关于Paint的一些用法。
    学会给paint添加阴影效果:

public void setShadowLayer(float radius, float dx, float dy, int shadowColor) 

这里不需要像面试一样问你Canvas、Drawable和Bitmap之间的关系,因为当你动手实现完这这个实例时,你自然会明白他们之间的关联。

具体实现参考:CircularImageView.java

实现GroupView+Adapter完成布局

onLayout在ViewGroup中是一个抽象方法,所以你自定义ViewGroup的话首先要重写它的一个实现。

我们需要给每一个可显示的子View设置它的显示位置(确定它四个点的位置):

public void layout(int l, int t, int r, int b)

在这个onLayout中我们先用最简单的线性布局一行排开所有的子View:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int viewGroupWidth = getMeasuredWidth();
        int painterPosX = l;
        int painterPosY = t;

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            int width = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();

            childView.layout(painterPosX, painterPosY, painterPosX + width, painterPosY + height);
            painterPosX += width;
        }
    }

验证onLayout如保布局,我们第一步是给ViewGroup添加子View,这里我们先用一种最简单的方式,在xml文件中直接添加:

    <net.goeasyway.coverflow.CoverflowView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <net.goeasyway.coverflow.CircularImageView
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:src="@mipmap/goeasyway"/>
        <net.goeasyway.coverflow.CircularImageView
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:src="@mipmap/goeasyway"/>
        <net.goeasyway.coverflow.CircularImageView
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:src="@mipmap/goeasyway"/>
        <net.goeasyway.coverflow.CircularImageView
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:src="@mipmap/goeasyway"/>
        <net.goeasyway.coverflow.CircularImageView
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:src="@mipmap/goeasyway"/>
    </net.goeasyway.coverflow.CoverflowView>

我们在CoverflowView下添加了5个CircularImageView的子View,在屏幕上看只能显示下4个:


当然,现在我们希望通过滑动能看到第5个子View。

实现滑动

其实滑动和动画效果的原理是一样的,就是让子View在短时间内连续地改变坐位或者大小(当然还有颜色等其他因素,也会产生动态的效果)。一秒内能显示的连续改变越多效果也就是越流畅(有点象录音的采样率)。

所以,在动手前你还需要检查一下下面罗列的概念或方法你是否有不明白的,可以在动手前稍稍了解一下:

获得滑动输入:onInterceptTouchEvent、onTouchEvent
计算:computeScroll、scrollBy & scrollTo、Scroller
调用View的绘制流程更新View:onMeasure、onLayout、onDraw

之后的问题会集中在怎么确定选中的Item,所以你会比较清楚一些常用到的坐标值各表示什么意思,如getX(), getRawX(), getScrollX()。

最后按这个面试题的要求添加一些功能:非选中的图像会缩小并且半透明显示,添加上Adapter让滑动人Item可以按需求增减。

最终我们实现一个类似的效果:

具体实现参考:CoverflowView.java

问题和优化

这是一个简单的实现方式,为什么要写一个简单的实现呢?我认为这样对于自己梳理知识(写文章)和对于想学习UI开发的朋友都是最有利的,我们先动手,然后发现我们遇到的问题,然后把这些问题解决了,然后获得提高!

  1. Adapter添加上的子View如何计算高度和宽度?
    本实例中我们写死了子View的大小。

  2. 可以添加fling实现更好的滑动体验。
    了解一些概念:VelocityTracker & Scroller.fling

  3. 了解addView & addViewInLayout的区别,我们还没有处理View的缓存的问题。
    不需要每次都把所有的子View添加到ViewGroup,而只添加在可视区域能界能出来的View。当子View滑出ViewGroup的可视区域时,如何处理?同理,刚滑进可视区时如何处理?

  4. 如何通知外部选中项发生了变化?
    这个问题应该不对实现吧。

这个实现还有很多可以优化的地方, 比如性能上,现在可能感觉不出来,但是当我们的Item比较复杂时(现在我们只是一个ImageView),而且Adapter中的数据较多时,就有可能会出现明显的卡顿现象。像不像ListView遇到的问题,其实是一样的,希望大家动手试试。

最后

有更好实现的,期待大家给我投稿,之后有时间我会再公布一版优化的版本。
未完待大家一起动手......

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

推荐阅读更多精彩内容