听歌识曲java_Android自定义View之继承扩展(仿网易云音乐听歌识曲)

本文介绍了如何在Android中模仿网易云音乐的听歌识曲界面,实现波纹涟漪效果。通过自定义View `RippleAnimationView` 和 `RippleCircleView`,利用动画集合实现圆环的缩放和渐变动画,从而达到预期效果。文章提供了源码和使用方法,并鼓励读者下载调试。
摘要由CSDN通过智能技术生成

前言

上篇文章说到了自定义View的组合实战,链接:Android自定义View之组合实战(以支付宝页面为例) ,感兴趣的同学可以看看。今天要分享的是一个模仿网易云音乐听歌识曲界面的自定义View,实现了一个波纹涟漪效果。

正文

废话不多说,惯例是上图,最终效果如下:

e8e562c29920

ripple.gif

分析:

首先拿到网易云音乐的听歌识曲效果,可以将其动画细分为两个,其一是缩放;其二是渐变,多个圆环同一时间执行动画集合,构成了最终效果!这样一来思路就有了,那么实现还有很大难度吗?

源码配合注释:

这个自定义View分为两个类实现,自定义属性我就不重复写,可以参考上一篇文章的内容:

RippleAnimationView.java

/**

* 波纹动画效果

* Created by fdm on 2017/7/10.

*/

public class RippleAnimationView extends RelativeLayout {

private int rippleType;

private int rippleColor;

private int rippleAmount;

private float rippleScale;

private float rippleRadius;

private int rippleDuration;

public Paint paint;

public float rippleStrokeWidth;

private TypedArray typedArray;

private AnimatorSet animatorSet;

private boolean animationRunning = false;

private ArrayList rippleViewList = new ArrayList<>();

//默认实心圆圈

private static final int DEFAULT_FILL_TYPE = 0;

//默认伸缩大小

private static final float DEFAULT_SCALE = 5.0f;

//默认圆圈个数

private static final int DEFAULT_RIPPLE_COUNT = 5;

//默认扩散时间

private static final int DEFAULT_DURATION_TIME = 2500;

public RippleAnimationView(Context context) {

super(context);

}

public RippleAnimationView(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

public RippleAnimationView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init(context, attrs);

}

private void init(final Context context, final AttributeSet attrs) {

//判断View当前是否处于 IDE 布局编辑(预览)状态,只有在编辑状态下才会返回true,

//在编写只有在运行时才能看到绘制效果的自定义View时,可以使用该方法查看布局预览。

if (isInEditMode()) {

return;

}

//加载自定义属性

typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleAnimationView);

rippleType = typedArray.getInt(R.styleable.RippleAnimationView_ripple_anim_type, DEFAULT_FILL_TYPE);

rippleColor = typedArray.getColor(R.styleable.RippleAnimationView_ripple_anim_color, ContextCompat.getColor(context, R.color.rippleColor));

rippleAmount = typedArray.getInt(R.styleable.RippleAnimationView_ripple_anim_amount, DEFAULT_RIPPLE_COUNT);

rippleScale = typedArray.getFloat(R.styleable.RippleAnimationView_ripple_anim_scale, DEFAULT_SCALE);

rippleRadius = typedArray.getDimension(R.styleable.RippleAnimationView_ripple_anim_radius, getResources().getDimension(R.dimen.rippleRadius));

rippleDuration = typedArray.getInt(R.styleable.RippleAnimationView_ripple_anim_duration, DEFAULT_DURATION_TIME);

rippleStrokeWidth = typedArray.getDimension(R.styleable.RippleAnimationView_ripple_anim_strokeWidth, getResources().getDimension(R.dimen.rippleStrokeWidth));

//注意回收TypedArray

typedArray.recycle();

int rippleDelay = rippleDuration / rippleAmount;

paint = new Paint();

paint.setAntiAlias(true);//抗锯齿

if (rippleType == DEFAULT_FILL_TYPE) {

rippleStrokeWidth = 0;

paint.setStyle(Paint.Style.FILL);

} else {

paint.setStyle(Paint.Style.STROKE);

}

paint.setColor(rippleColor);

LayoutParams rippleParams = new LayoutParams((int) (2 * (rippleRadius + rippleStrokeWidth)), (int) (2 * (rippleRadius + rippleStrokeWidth)));

rippleParams.addRule(CENTER_IN_PARENT, TRUE);

//分析该动画后将其拆分为缩放、渐变

ArrayList animatorList = new ArrayList<>();

for (int i = 0; i < rippleAmount; i++) {

RippleCircleView rippleView = new RippleCircleView(this, context);

addView(rippleView, rippleParams);

rippleViewList.add(rippleView);

//ScaleX缩放

final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(rippleView, "ScaleX", 1.0f, rippleScale);

scaleXAnimator.setRepeatCount(ObjectAnimator.INFINITE);//无限重复

scaleXAnimator.setRepeatMode(ObjectAnimator.RESTART);

scaleXAnimator.setStartDelay(i * rippleDelay);

scaleXAnimator.setDuration(rippleDuration);

animatorList.add(scaleXAnimator);

//ScaleY缩放

final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(rippleView, "ScaleY", 1.0f, rippleScale);

scaleYAnimator.setRepeatCount(ObjectAnimator.INFINITE);//无限重复

scaleYAnimator.setRepeatMode(ObjectAnimator.RESTART);

scaleYAnimator.setStartDelay(i * rippleDelay);

scaleYAnimator.setDuration(rippleDuration);

animatorList.add(scaleYAnimator);

//Alpha渐变

final ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(rippleView, "Alpha", 1.0f, 0f);

alphaAnimator.setRepeatCount(ObjectAnimator.INFINITE);//无限重复

alphaAnimator.setRepeatMode(ObjectAnimator.RESTART);

alphaAnimator.setStartDelay(i * rippleDelay);

alphaAnimator.setDuration(rippleDuration);

animatorList.add(alphaAnimator);

}

animatorSet = new AnimatorSet();

animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());

animatorSet.playTogether(animatorList);

}

/**

* 开始动画

*/

public void startRippleAnimation() {

if (!isRippleRunning()) {

for (RippleCircleView rippleView : rippleViewList) {

rippleView.setVisibility(VISIBLE);

}

animatorSet.start();

animationRunning = true;

}

}

/**

* 停止动画

*/

public void stopRippleAnimation() {

if (isRippleRunning()) {

Collections.reverse(rippleViewList);

for (RippleCircleView rippleView : rippleViewList) {

rippleView.setVisibility(INVISIBLE);

}

animatorSet.end();

animationRunning = false;

}

}

/**

* 是否正在执行

*

* @return boolean isRippleRunning

*/

public boolean isRippleRunning() {

return animationRunning;

}

}

RippleCircleView.java

/**

* Draw Circle

* Created by fdm on 2017/7/10.

*/

public class RippleCircleView extends View {

private RippleAnimationView rippleAnimationView;

public RippleCircleView(Context context) {

super(context);

}

public RippleCircleView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public RippleCircleView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

public RippleCircleView(RippleAnimationView rippleAnimationView, Context context) {

super(context);

this.rippleAnimationView = rippleAnimationView;

this.setVisibility(View.INVISIBLE);

}

@Override

protected void onDraw(Canvas canvas) {

int radius = (Math.min(getWidth(), getHeight())) / 2;

canvas.drawCircle(radius, radius, radius - rippleAnimationView.rippleStrokeWidth, rippleAnimationView.paint);

}

}

源码不难理解,继承了RelativeLayout,然后再画几个圆,执行相应动画,效果就出来了。这里值得一提的是:

if (isInEditMode()) {

return;

}

···

scaleXAnimator.setStartDelay(i * rippleDelay);

第一,isInEditMode判断View当前是否处于 IDE 布局编辑(预览)状态,只有在编辑状态下才会返回true,在编写只有在运行时才能看到绘制效果的自定义View时,可以使用该方法查看布局预览,避免IDE报错。

第二,这个setStartDelay方法延迟执行动画,在这里是实现波纹效果的重点,首先我们定义了圆的个数(默认为5),然后往里面添加了5个圆,然后将动画执行时间(duration)均分,每个圆依次分档执行setStartDelay方法,结合缩放、渐变动画,最终完成了我们期待的效果。为了方便使用,暴露几个常用方法提供调用即可!

使用:

android:id="@+id/layout_RippleAnimation"

android:layout_width="360dp"

android:layout_height="360dp"

app:ripple_anim_amount="5"

app:ripple_anim_color="@color/rippleColor"

app:ripple_anim_duration="2500"

app:ripple_anim_radius="@dimen/rippleRadius"

app:ripple_anim_scale="6"

app:ripple_anim_type="fillRipple">

android:id="@+id/ImageView"

android:layout_width="100dp"

android:layout_height="100dp"

android:layout_centerHorizontal="true"

android:layout_centerVertical="true"

android:src="@mipmap/music" />

总结

这次借着模仿网易云音乐听歌识曲界面实践了自定义View的继承扩展方式,还原度上还有些细节需要完善,这里主要是为了自定义View,就不再细究了,感兴趣的可以下载源码进行调试。

国际惯例:源码送上https://github.com/brucevanfdm/NetEasyRipple 欢迎star/fork,并提出宝贵意见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值