1、效果演示
点赞后,会有动画效果,绘制箭头。
2、实现步骤(代码有详细注释)
1、LikeClickView
package com.ywz.likeclickviewdemo.view;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.ywz.likeclickviewdemo.R;
import com.ywz.likeclickviewdemo.utils.SystemUtil;
public class LikeClickView extends View {
private boolean isLike;
private Bitmap likeBitmap;
private Bitmap unLikeBitmap;
private Bitmap shiningBitmap;
private Paint bitmapPaint;
private int left;
private int top;
private float handScale = 1.0f;
private float centerX;
private int centerY;
public LikeClickView(Context context) {
this(context, null, 0);
init();
}
public LikeClickView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
init();
}
public LikeClickView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.JiKeLikeView);
isLike = typedArray.getBoolean(R.styleable.JiKeLikeView_is_like, false);//attrs中的属性
typedArray.recycle();//回收资源
init();
}
private void init() {
Resources resources = getResources();
likeBitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_message_like);
unLikeBitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_message_unlike);
shiningBitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_message_like_shining);
bitmapPaint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = 0;
int measureHeight = 0;
int maxHeight = unLikeBitmap.getHeight() + SystemUtil.dp2px(getContext(), 20);
int maxWidth = unLikeBitmap.getHeight() + SystemUtil.dp2px(getContext(), 30);
// 拿到当前控件的测量模式
int mode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (mode != MeasureSpec.EXACTLY) {
//测量模式未指定 如果有背景 背景多大 我们这个控件就有多大
int suggestedMinimumWidth = getSuggestedMinimumWidth();
int suggestedMinimumHeight = getSuggestedMinimumHeight();
if (suggestedMinimumWidth == 0) {
measureWidth = maxWidth;
} else {
measureWidth = Math.min(suggestedMinimumWidth, maxWidth);
}
if (suggestedMinimumHeight == 0) {
measureHeight = maxHeight;
} else {
measureHeight = Math.min(suggestedMinimumHeight, maxHeight);
}
} else {
// 测量模式指定 根据用户定义大小来判断
measureWidth = Math.min(maxWidth, widthSize);
measureHeight = Math.min(maxHeight, heightSize);
}
setMeasuredDimension(measureWidth,measureHeight);
getPading(measureWidth,measureHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap handBitmap = isLike ? likeBitmap : unLikeBitmap;
// 使用 canvas scale及其他的效果方法时 必须 先调用save 然后再调用restore (这两个方法成对出现的)
canvas.save();
canvas.scale(handScale, handScale, centerX, centerY);
canvas.drawBitmap(handBitmap, left, top, bitmapPaint);
canvas.restore();
if (isLike) {
canvas.drawBitmap(shiningBitmap, left + 10, 0, bitmapPaint);//绘制星星效果
}
}
public void getPading(int measureWidth, int measureHeight) {
int bitmapWidth = likeBitmap.getWidth();//取得bitmap的宽度
int bitmapHeight = likeBitmap.getHeight();
left = (measureWidth - bitmapWidth)/2; //求出间距
top = (measureHeight - bitmapHeight)/2;
int width = getMeasuredWidth();//返回的值是 View在Measure过程中测量的宽 / 高
int height = getMeasuredHeight();
centerX = width / 2;//取得中心点
centerY = height / 2;
}
// 当这个自定义View 从 界面 脱离消失的时候
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
likeBitmap.recycle();//回收资源
unLikeBitmap.recycle();
shiningBitmap.recycle();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
onClick();
break;
default:
break;
}
return super.onTouchEvent(event);
}
// 待完善 动画的处理
private void onClick() {
isLike = !isLike;
// ObjectAnimator handScale = ObjectAnimator.ofFloat(this, "handScale", 1.0f, 0.8f, 1.0f);
// handScale.setDuration(250);
// handScale.start();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f);
valueAnimator.setDuration(250);
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
handScale = animatedValue;
invalidate();//再次调用onDraw,重新绘制
}
});
}
/**
* 使用ObjectAnimator 系统会自动调用 该属性的 Set 方法
* @param value
*/
public void setHandScale(float value) {
this.handScale = value;
invalidate();
}
}
2、attrs
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="JiKeLikeView">
<attr name="is_like" format="boolean" />
</declare-styleable>
</resources>
3、布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.ywz.likeclickviewdemo.view.LikeClickView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:is_like="true" />
</LinearLayout>
3、源码下载
https://download.csdn.net/download/qq_31776219/12258870
4、参考资料
1.Android自定义View:源码解析通过getWidth() 与 getMeasuredWidth()获取宽高的区别:https://blog.csdn.net/carson_ho/article/details/98026070
2、动画
1、原生动画
①补间动画
平移:TranslateAnimation
旋转:RotateAnimation
缩放:ScaleAnimation
渐变:AlphaAnimation
小案例:给RecyclerView设置动画。
②属性动画
ObjectAnimatior: translation(x或y),rotation(x或y),scale(x或y)
ValueAnimator
ValueAnimator 类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;
ObjectAnimator 类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;
ObjectAnimator 类针对的是任意对象 & 任意属性值,并不是单单针对于View对象
如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的set() & get()
③帧动画
AniamteDrawable
3、事件分发
onTouchEvent:触摸事件的处理
dispatchTouchEvent:传递触摸事件
onInterceptTouchEvent:拦截事件传递
requestDisallowedInterceptTouchEvent(boolean disallowIntercept):请求自己的父布局不要拦截事件
1、安卓中事件的消费会 优先让子View去处理。
2、如果ViewGroup很想处理这个事件怎么办? onInterceptTouchEvent
3、requestDisallowedInterceptTouchEvent 请求ViewGroup不要去处理这个事件。