美文网首页Andorid的好东西互联网研究所自定义views
自定义控件 | 仿《最美有物》点赞效果

自定义控件 | 仿《最美有物》点赞效果

作者: 岱zy | 来源:发表于2017-07-26 16:36 被阅读4340次

    继最美有物之后,又在饿了么上看到了类似的控件。果然需求这个东西是做不完的,以为功能做好就OK了吗,不要停,这里还差一个玩出花来的点赞效果!

    直接上动图,看看两家的实现效果。

    最美有物 饿了么

    这样可爱的点赞特效,不得不给个好评啊, 有没有!

    当然光点赞好评是不行的,今天得自己动手撸一个这样的笑脸点赞控件。

    主要流程和实现分析

    以最美有物的点赞效果为模板(相较饿了么更丰富),从整体的操作流程来分析:

    点击触发 :

    1. 颜色切换(选中黄,未选中白)

    2. 按比例拉伸(两个笑脸同时升高,显示点赞比例的高度)

    3. 展示数据文本(百分比数据显示)

    4. 显示选中部分的笑脸动画(这一步与3文本数据同时进行,在拉伸至最高处停留后)

    5. 缩回至原始大小,保持选中状态

    通过这个流程的分析,我们可以把主要的功能点划分为以下两类:

    ​ 1.动画类 : 脸部动画,拉伸动画

    ​ 2.控制类: 颜色切换,数据显示,拉伸比例

    在自定义View的过程中,动画特效是最吸引人,也是最复杂的部分,虽然现在有高效炫酷的矢量动画库供我们选择, 但是基础动画的组合也是相当有用的,重点是发挥想象力。

    其中,脸部动画在解压apk后找到相关图片,不出所料是个帧动画。

    而主要的难点就在于如何进行拉伸操作。对于将一个圆形从中间拉伸成长条...

    最开始想到的方案是拼接图形,即通过圆形 + 矩形 + 圆形的方式叠加这个控件。通过调节中间矩形的高度,来控制拉伸操作。但是这种方式结构略复杂,需要在一小块地方摆上三个图形,还要带上最外层的笑脸动画,还没写代码就感觉应该是性能低下的方案,另外一个致命的问题就是,不能有描边!大家可以参考饿了么的实现效果,在有描边的情况下,形状拼接的方案明显不可行。遂放弃。

    而最终采用的则是使用圆角矩形作为外层Layout背景,通过控制内部笑脸的marginBottom,来动态的调节Layout的高度。这样,即可以保持笑脸始终处于控件的上部,同时也能控制相对简单的结构。

    拉伸操作的实现

    首先我们先简单的模拟下通过marginBottom控制拉伸的效果。

    在布局里设置一个LinearLayout ,里面只有一个ImageView。用一个seekBar来模拟效果。

    Layout:

     <LinearLayout
            android:id="@+id/backGround"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:background="@drawable/yellow_background"
            android:layout_above="@+id/seekBar"
            android:layout_centerHorizontal="true">
    
            <ImageView
                android:id="@+id/smileFace"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/like_1"/>
        </LinearLayout>
    

    这里Linearlayout中设置了一个背景,是自定义的圆角矩形shape,通过调大圆角,使其显示为一个正圆。

    Activity:

        @Override
                public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                    LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)smileFace.getLayoutParams();
                    layoutParams.bottomMargin = i*3;
                    smileFace.setLayoutParams(layoutParams);
                }
    

    通过获取SmileFace的LayoutParams,通过Seekbar设置其下边距bottomMargin,来控制高度。

    效果如下所示:

    2017-07-26_13-46-10.gif

    这样拉伸的原理就很清楚了。

    我们需要在自定义控件中完成上述操作,并用属性动画替换掉seekBar。

    自定义控件的封装

    子控件的布局

    考虑到实现目标里有两个并排的笑脸控件,这里采用继承LinearLayout的方式,可以把两个控件及中间的分割线直接摆放进去。

    首先初始化两个脸部动画的ImageView及动画资源,以及两个显示点赞比例的数字及文本的TextView。
    在初始化的时候设置好相关参数,提取出默认值并提供方法设置相关参数。然后把百分比,文字,包含笑脸的Layout,都添加到另外一个Linearlayout中,然后再将喜欢不喜欢添加到当前自定义控件中。

        //初始化图片
            imageLike = new ImageView(getContext());
            //添加动画资源  获得帧动画
            imageLike.setBackgroundResource(R.drawable.animation_like);
            animLike = (AnimationDrawable) imageLike.getBackground();
            //初始化文字
            likeNum = new TextView(getContext());
            likeNum.setText(like + "%");
            likeNum.setTextColor(defalutTextColor);
            TextPaint likeNumPaint = likeNum.getPaint();
            likeNumPaint.setFakeBoldText(true);
            likeNum.setTextSize(20f);
            likeText = new TextView(getContext());
            likeText.setText(defaultLike);
            likeText.setTextColor(defalutTextColor);
    
            .....
    
            disAll.addView(disNum, params);
            disAll.addView(disText, params);
            disAll.addView(disBack, params);
            likeAll.addView(likeNum, params);
            likeAll.addView(likeText, params);
            likeAll.addView(likeBack, params);
    
    

    这里同时还要注意隐藏文字,以及默认设置为未选中状态。这一段代码虽然挺多,其实也只是在View中做了xml里的事情,了解了整体结构后其实非常简单,实际上直接写好XML再加载也是没问题的。

    整体流程和动画分析

    控件的事件其实只有两个点击事件,需要注意的是动画流程的控制。
    1.拉伸属性动画 》 2.表情帧动画 》3.与2同时进行的平移动画

    直接在控件设置onClickListener。点击开始执行拉伸动画。并在动画开始后限制点击事件,流程结束后释放。避免重复点击动画错乱。同时给属性动画设置监听,在拉伸执行完毕后,继续执行面部的动画。

    通过属性动画,将喜欢不喜欢的数字比例设置为两个笑脸的bottomMargin,这里由于Max是使用两个数字和,显示的高度会依据数字的大小有差别,也可以设置为一个固定值,完全暗战比例来显示高度,这个可以依据自己的数据源和需求修改。由于在属性动画中同时设置两个高度,所以需要通过判断限制高度,当前magrin与达到数据要求时停止,从而有比例低的一方会停止拉伸。

    拉伸的属性动画,与之对应还有一个缩回的动画,如果有需要还可以加上插值器,优化弹起的效果。

    //背景伸展动画
        public void animBack() {
            //动画执行中不能点击
            imageDis.setClickable(false);
            imageLike.setClickable(false);
    
            final int max = Math.max(like * 4, disLike * 4);
            animatorBack = ValueAnimator.ofInt(5, max);
            animatorBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int magrin = (int) animation.getAnimatedValue();
                    LayoutParams paramsLike
                            = (LayoutParams) imageLike.getLayoutParams();
                    paramsLike.bottomMargin = magrin;
    
                    if (magrin <= like * 4) {
                        imageLike.setLayoutParams(paramsLike);
                    }
                    if (magrin <= disLike * 4) {
                        imageDis.setLayoutParams(paramsLike);
                    }
                }
            });
            isClose = false;
            animatorBack.addListener(this);
            animatorBack.setDuration(500);
            animatorBack.start();
        }
    

    拉伸动画结束后,帧动画与平移动画共同构成了面部的表情动画,通过补间动画的配合使面部表情更加生动。同时也是用补间动画的结束监听来继续执行动画恢复原始状态。下图的animLike为帧动画,objectX,Y为对应轴的平移动画。

    拉伸和恢复动画的结束监听,通过isClose区分.以及平移动画。

     @Override
        public void onAnimationEnd(Animator animation) {
            //重置帧动画
            animDis.stop();
            animLike.stop();
    
            //关闭时不执行帧动画
            if (isClose) {
                //收回后可点击
                imageDis.setClickable(true);
                imageLike.setClickable(true);
                //隐藏文字
                setVisibities(GONE);
                //恢复透明
                setBackgroundColor(Color.TRANSPARENT);
                return;
            }
            isClose = true;
    
            if (type == 0) {
                animLike.start();
                objectY(imageLike);
            } else {
                animDis.start();
                objectX(imageDis);
            }
        }
    
    public void objectY(View view) {
            ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", -10.0f, 0.0f, 10.0f, 0.0f, -10.0f, 0.0f, 10.0f, 0);
            animator.setRepeatMode(ObjectAnimator.RESTART);
            //animator.setRepeatCount(1);
            animator.setDuration(1500);
            animator.start();
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    setBackUp(); //执行回弹动画
                }
            });
        }
    

    最终效果

    完整的动画流程实现后,我们的控件就基本完成了。在XML中直接使用,并使用setNum设置数字,基本实现了最美有物的点赞控件效果,简单的撸了一遍。看下最终效果图吧。

            smileView = (SmileView) findViewById(R.id.smileView);
            smileView.setNum(60,40);
    
    2017-07-26_15-18-35.gif

    附上完整的demo地址。

    相关文章

      网友评论

      • 楷桐:Math.max(like * 4, disLike * 4); 为什么要乘4 ???
        岱zy: @梧桐树下梧桐树 因为数值太小所以动画的幅度比较小,,笑脸伸出来都很矮,所以*4上下更明显一些
      • SheepYang:效果不错,很萌啊:grin:
      • 的一幕:楼主给力,会细心观察美的存在哈。。。。。。
      • MISSGY_:最美有物的画报很新颖啊~~能分享一下嘛:smirk:
        岱zy: @可我就是爱她啊 应你要求,没问题的😏
      • darktiny:饿了么的实现是 AnimatedVectorDrawable
        已来的主人翁:@岱zy 楼主真谦虚 值得学习:sunglasses:
        岱zy:多谢告知~
        AnimatedVectorDrawable这个不是很熟悉,又有了需要加深学习的知识点 :smile:
      • X卢飝B:谢谢 楼主真好 666
      • 哈哈羊:有想做软件开发和网站建设的朋友们,欢迎咨询我,qq849440483😀
      • 眸眼夜幕下的阴影:666666666666666666666666666666
      • 伟大的小炮殿下:厉害厉害,当初看到这个就很想实现,一直没时间,现在先看看大神的分析再撸一遍
      • 旋哥:厉害
      • YuGoal:真棒:+1:
      • 小彤花园:这个真好玩
      • c3db54854753:棒,👍,收藏了,谢谢楼主

      本文标题:自定义控件 | 仿《最美有物》点赞效果

      本文链接:https://www.haomeiwen.com/subject/zuwwkxtx.html