Android仿知乎客户端关注和取消关注的按钮点击特效实现思路详解

先说明一下,项目代码已上传至github,不想看长篇大论的也可以先去下代码,对照代码,哪里不懂点哪里。
代码在这https://github.com/zgzczzw/ZHFollowButton

前几天发现知乎关注的点击效果确实赞,查了一下实现方式,刚好看到这个问题,花了一天时间终于把这个效果实现了,现在来回答一下,很不幸,楼上各位的答案都不全对,且听我一一道来。

首先,我先详细观察了一些知乎的效果,其中有一个很神奇的地方,如图:


注意看第二张图,这个圆形在扩散的时候,圆形底下的字还在,而且新的字也在圆形上,就这个效果实现起来最难。

首先看一下楼上各位的回答,归纳来说,一共有2种实现方式,ripple效果和用paint在canvas上手动画圆

ripple:

ripple即波纹效果,是android API 21以后引入的一种material design的元素,是触摸反馈的一种,也就是说点击的时候会出现水波扩散的样式,demo(见最后)中第一个按钮就是用了ripple效果。

实现方式很简单,实现一个这样的drawable

第一个color是波纹颜色,item里面指定background正常的颜色,可以是一个shape,也可以是一个drawable,还可以是一个selector。

设置为按钮的background即可

如果整个程序的theme用了meterial,那基本所有的带点击效果的控件,比如button都自带这个波纹效果。不过需要注意的是这一套API是21以后才提供的,所以需要做兼容处理。

效果如下:

从图中可以看出即使我设置了波纹为红色(#FF0000),点击后的效果也是淡红色,我猜测因为是水波纹效果,为了不影响按钮本身展示的内容,android系统自动做了透明度的处理,另外从图中也可以明显的看出,水波纹和显示的内容是上下两层的,互不影响,水波纹是在background层面上。这个效果做普通的点击反馈还不错,但绝对实现不出知乎这种用波纹刷新出内容的效果。所以很容易能看出知乎的点击效果不是用ripple做出来的。

Paint在canvas上画圆

@chaossss 所说的用 paint在点击的地方画圆形,然后让画的圆形半径慢慢变大,实现出扩散出去的样式,我实现了一下,代码如下:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mShouldDoAnimation) {
mMaxRadius = getMeasuredWidth() + 50;
if (mRevealRadius > mMinBetweenWidthAndHeight / 2)
mRevealRadius += mRevealRadiusGap * 4;
else
mRevealRadius += mRevealRadiusGap;//半径变大
Paint mPaint = new Paint();
if (!mIsPressed) {
mPaint.setColor(Color.WHITE);
} else {
mPaint.setColor(Color.RED);
}//设置画笔颜色
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);

if (mRevealRadius <= mMaxRadius) {
//一定时间后再刷新
postInvalidateDelayed(INVALIDATE_DURATION);
} else {
if (mIsPressed) {
setTextColor(Color.WHITE);
this.setBackgroundColor(Color.RED);
} else {
setTextColor(Color.BLACK);
this.setBackgroundColor(Color.WHITE);
}
mShouldDoAnimation = false;
invalidate();
}
}
}

效果如图:

本来觉得差不多就是这样,但是跟知乎的效果比较一下,还是能发现差别的。用paint画圆能实现的是在点击的地方画一个圆,然后半径慢慢变大慢慢扩散。但是问题在于,画的这个圆会盖住显示的内容,而且画的圆上也不能显示内容。我试过用drawText,也实现不了字和圆一起的效果,解决方法只有,画的过程中改背景色和上面文字。

然后,画完圆之后把圆擦掉,把下面的背景色和文字显示出来。

这样就会出现一次文字闪烁的问题,首先文字会消失掉,然后画完圆之后才显示出来。因为圆在扩散的时候是看不到文字的,只有等圆消失了,文字才能显示出来。而知乎的效果是文字和圆一起刷出来,而且底下的文字还在,中间也没有文字闪烁的问题,整个过程行云流水,看起来很顺畅,好像用圆形揭开了幕布一样。

综上所述,楼上所有的答案都是答主们看到这个效果后第一反应的实现,其实如果不是我自己实现了一下,真的以为第二种方法就是知乎采用的,但是目前看来,很遗憾,知乎采用了一种更好的方式来实现这个效果。

那怎么办呢,我也没什么思路,怎么才能在画圆的时候把字也画在圆上,然后圆下面的背景也还有呢。没什么思路,看看知乎的代码吧,反编译。

反编译的过程我简单说一下:

到知乎官网下载最新的知乎apk

用apktool反编译apk,得到资源文件

在资源文件中搜索follow,这里一开始我搜的是ripple,因为我觉得这个效果总归应该和ripple有关,没结果,于是搜了follow,没想到还真搜出来了。

RevealFollowButton这明显就是我们要的波纹展开的控件,这就好说了,下一步就是去代码里找到这个控件了。这里要记一下,这个控件的位置com.zhihu.android.app.ui.widget.RevealFollowButton。

反编译代码

将apk改名成rar,打开,可以找到里面的class文件

知乎用了multidex,所以会有两个class文件,都拖出来放在dex2jar里反编译一下,就能生成两个jar包了,把jar包放在GUI里看一下,就能看到代码了,虽然代码被混淆过,但是基本逻辑还是能看出来的。

然后根据前面xml里的路径找到RevelFollowButton的位置,打开代码看就可以了。

这是类的继承关系,RevealFollowButton继承自RevealFrameLayout,然后继承自ZHFrameLayout,这个ZHFrameLayout的父类就是FrameLayout了,从名字我们能看出,RevelFollowButton和RevealFrameLayout就是这个效果实现的两个类了。

看到这个效果的实现是基于Framelayout,我就知道我们之前讨论的方法其实都走错了方向,如果告诉你用framelayout来实现这个效果,你会怎么做?

我的想法是加入两个TextView到这个layout里,然后一个Visible一个gone,如此切换,后来看过代码后,也证明我的这个想法是对的。

看,这里有两个TextView。如此的话,其实切换TextView是很容易实现的,问题是怎么实现波纹切换的效果,那第一件事就是看onDraw函数了,对于GroupView来说是drawChild方法。

RevealFollowButton的drawChild方法没什么内容,基本是调用了父类,那么我们来看RevealFrameLayout的drawChild方法。

这里有两部分逻辑,如果满足一个条件,就做第一部分,一开始我也不知道这个条件是什么,混淆后的代码能看懂大逻辑,像这种小逻辑只能走一步看一步了。所以假设这个条件永远false吧,看第二部分,看到这里瞬间明白了,原来是采用切割画布的方式,把画布切成一个圆的,就能做到显示的内容也在圆上,而不是内容被覆盖在圆下面了。然后同理,把这个圆形区域不断扩大,然后不断刷新,就是实现波形刷出内容的效果了。代码如下吧

protected boolean drawChild(Canvas canvas, View paramView, long paramLong) {
int i = canvas.save();
mPath.reset();
//mCenterX mCenterY是点击的位置,在onTouchEvent里设置
//mRevealRadius是圆的半径,会渐渐变大
mPath.addCircle(mCenterX, mCenterY, mRevealRadius, Path.Direction.CW);
canvas.clipPath(this.mPath);
boolean bool2 = super.drawChild(canvas, paramView, paramLong);
canvas.restoreToCount(i);
return bool2;
}

按照上面说的,肯定还有一个类似于定时器的东西,能不断改变圆形的半径,然后刷新,其实这个在代码里找找很容易就找到了。RevealFrameLayout里除了这个drawChild,没有别的代码了。所以我们来看RevealFollowButton。

RevealFollowButton里面跟定时器有关的就是这句了

一个Animator对象,其实这句代码我是没看懂的,但逻辑很简单,设置一个Animator,定时500ms,在这个过程中修改圆形半径,然后刷新。

Math.hypot(getWidth(), getHeight()))

其中这个方法是根据勾股定理获取三角形的斜边长度,想想我们所要绘制的圆形半径最长是多少,没错,就是TextView的对角线长度。所以,整个逻辑就很简单了。

我搞了下代码,就这样吧

整个方法的代码如下吧,还包括控制FollowTv和unFollowTv哪个显示

protected void setFollowed(boolean isFollowed, boolean needAnimate) {
mIsFollowed = isFollowed;
if (isFollowed) {
mUnFollowTv.setVisibility(View.VISIBLE);
mFollowTv.setVisibility(View.VISIBLE);
mFollowTv.bringToFront();
} else {
mUnFollowTv.setVisibility(View.VISIBLE);
mFollowTv.setVisibility(View.VISIBLE);
mUnFollowTv.bringToFront();
}
if (needAnimate) {
ValueAnimator animator = ObjectAnimator.ofFloat(mFollowTv, "empty", 0.0F, (float) Math.hypot(getMeasuredWidth(), getMeasuredHeight()));
animator.setDuration(500L);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRevealRadius = (Float) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
}

根据当前状态把Follow的Textview或UnFollow的TextView显示出来,然后设置一个定时器不断扩大所要绘制圆的半径,根据这个半径裁剪画布成一个渐渐变大的圆形,然后内容就渐渐显示出来了。

这个效果实现出来之后,试着运行一下,还不错,但是总觉得有地方不对,于是细细观察,终于发现了,知乎的那个效果在刷新的时候,底下的背景不是白色的,还是之前的状态,比如要变成关注的时候,背景中的未关注还是在的,而我们实现的这个,刷新的时候背景是白色的。

这是知乎的

这是我的

所以还是没有知乎那么行云流水,所以我们是少了什么吗。这时候想起来了,之前在RevealFrameLayout的drawChild里有一个判断条件,当时我们不知道它的逻辑是干什么的,现在看来。那部分逻辑就是处理这个的,画子控件的时候,要画两个,FollowTextView和UnFollowTextView,要随圆形刷出的控件我们采用裁剪画布的方式慢慢画出。那作为背景的另一个控件就不需要慢慢画出,只要完全画出来就行了。所以,猜想这里这个判断条件就是判断当前控件是不是要随圆形刷出的控件,如果不是,就直接画出来就行了。所以修改代码如下:

protected boolean drawChild(Canvas canvas, View paramView, long paramLong) {
if (drawBackground(paramView)) {
return super.drawChild(canvas, paramView, paramLong);
}
int i = canvas.save();
mPath.reset();
mPath.addCircle(mCenterX, mCenterY, mRevealRadius, Path.Direction.CW);
canvas.clipPath(this.mPath);
boolean bool2 = super.drawChild(canvas, paramView, paramLong);
canvas.restoreToCount(i);
return bool2;
}

判断的方法如下:

private boolean drawBackground(View paramView) {
if (mIsFollowed && paramView == mUnFollowTv) {
return true;
} else if (!mIsFollowed && paramView == mFollowTv) {
return true;
}
return false;
}

至此,整个效果就和知乎完全一样了,刷新过程行云流水,非常赞。效果如下


实现代码已上传至github:

https://github.com/zgzczzw/ZHFollowButton

以上所述是小编给大家介绍的Android仿知乎客户端关注和取消关注的按钮点击特效实现思路详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • android中在Activity中响应ListView内部按钮的点击事件的两种方法

    最近交流群里面有人问到一个问题:如何在Activity中响应ListView内部按钮的点击事件,不要在Adapter中响应? 对于这个问题,我最初给他的解答是,在Adapter中定义一个回调接口,在Activity中实现该接口,从而实现对点击事件的响应. 下班后思考了一下,觉得有两种方式都能比较好的实现:使用接口回调和使用抽象类回调. 正好可以复习一下接口和抽象类的区别,于是写了两个Demo: 1.使用接口回调: Adapter类 package com.ivan.adapter; import

  • Android悬浮按钮点击返回顶部FloatingActionButton

    先看一下Android悬浮按钮点击回到顶部的效果: FloatingActionButton是Design Support库中提供的一个控件,这个控件可以轻松实现悬浮按钮的效果 首先,要在项目中使用这个悬浮按钮就要先把design这个包导入项目 gradle中加入依赖 compile 'com.android.support:design:25.0.0' 接下来就是在xml中使用: 我这里是放置一个listView模拟返回顶部 <?xml version="1.0" encodi

  • Android防止按钮过快点击造成多次事件的解决方法

    问题 onClick事件是Android开发中最常见的事件.比如,一个submitButton,功能是点击之后会提交一个订单, 则一般代码如下,其中submitOrder()函数会跳转到下一页进行处理 : //代码0 submitButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { submitOrder(); } }); 正常情况下这段代码是没什么问题的,但是Andro

  • 基于Android实现点击某个按钮让菜单选项从按钮周围指定位置弹出

    Android Material Design:PopupMenu Android Material Design 引入的PopupMenu类似过去的上下文菜单,但是更灵活. 如图所示: 现在给出实现上图PopupMenu的代码. 本例是一个普通的Button触发弹出PopupMenu. 测试的MainActivity.java : package zhangphil.materialdesign; import android.app.Activity; import android.os.B

  • Android点击按钮返回顶部实现代码

    点击按钮返回顶部,直接上代码吧 布局文件 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent

  • 实例详解Android解决按钮重复点击问题

    为了防止用户或者测试MM疯狂的点击某个button,写个方法防止按钮连续点击.具体实例代码如下所示: public class BaseActivity extends Activity { protected boolean isDestroy; //防止重复点击设置的标志,涉及到点击打开其他Activity时,将该标志设置为false,在onResume事件中设置为true private boolean clickable=true; @Override protected void on

  • Android模拟开关按钮点击打开动画(属性动画之平移动画)

    在Android里面,一些炫酷的动画确实是很吸引人的地方,让然看了就赏心悦目,一个好看的动画可能会提高用户对软件的使用率.另外说到动画,在Android里面支持两种动画:补间动画和属性动画,至于这两种动画的区别这里不再介绍,希望开发者都能在使用的过程中体会两者的不同. 本文使用属性动画完成,说到属性动画,肯定要提到 JakeWharton大神写的NineOldAndroids动画库,如果你的app需要在android3.0以下使用属性动画,那么这个库就很有作用了,如果只需要在高版本使用,那么直接

  • Android Button按钮的四种点击事件

    本文实例为大家分享了安卓Button按钮的四种点击事件,供大家参考,具体内容如下 第一种:内部类实现 1.xml里面先设置Button属性 <Button android:id="+@id/button1"; android:layout_width="wrap_parent"; android:layout_height="wrap_parent" android:text="按钮"/> 2.找到按钮 Butto

  • Android实现按钮点击效果

    1.首先创建一个按钮 <Button android:id="@+id/click" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="点击变色" android:background="@drawable/btn_st" android:gravity="center&

  • Android实现点击AlertDialog上按钮时不关闭对话框的方法

    本文实例讲述了Android实现点击AlertDialog上按钮时不关闭对话框的方法.分享给大家供大家参考.具体如下: 开发过程中,有时候会有这样的需求: 点击某个按钮之后显示一个对话框,对话框上面有一个输入框,并且有"确认"和"取消"两个按钮.当用户点击确认按钮时,需要对输入框的内容进行判断.如果内容为空则不关闭对话框,并toast提示. 使用AlertDialog.Builder创建对话框时,可以使用builder.setNegativeButton和build

随机推荐