android触摸效果,Android开发进阶:仿MIUI12控件触摸反馈效果(下沉+倾斜)附源码...

本文介绍了如何在Android中模仿MIUI12的控件触摸反馈效果,包括内圈按压时控件下沉和阴影变化,以及外圈按压时的倾斜效果。通过自定义PressFrameLayout,利用ObjectAnimator和Camera实现动画效果,并提供了详细的代码实现和测试布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单模仿了下MIUI12里控件的触摸反馈效果,转载请标明出处

效果简述

按压控件内圈区域,控件整体缩小,高度降低(阴影消失)

5bec6f9f6737

按压内圈

按压控件外圈区域,依据触摸点控件以中心为支点,向触摸点倾斜

5bec6f9f6737

按压外圈

实现原理

按压内圈效果

内圈效果包含控件整体缩放、外圈阴影变化。

整体缩放:给控件的scaleX、scaleY添加ObjectAnimator动画

阴影变化:绘制控件背景时,使用paint.setShadowLayer()方法绘制控件阴影。使用ObjectAnimator动画修改setShadowLayer里的radius,实现阴影的半径变化。

按压外圈倾斜效果

onDraw()绘制时,修改camera的的旋转角度。

源码

主要代码

/**

* 作者: 哒啦啦

* 创建时间: 2021/1/20

* 描述: 简单模仿MIUI12控件按压效果

* 内圈按压:控件整体下沉并伴随阴影变化

* 外圈按压:控件向按压位置倾斜

*/

public class PressFrameLayout extends FrameLayout {

private int width = 0;//父布局宽度

private int height = 0;//父布局高度

private int padding;//为阴影和按压变形预留位置

private int cornerRadius;//控件圆角

private float shadeOffset;//阴影偏移

Paint paintBg = new Paint(Paint.ANTI_ALIAS_FLAG);

Camera camera = new Camera();

float cameraX = 0f;//触摸点x轴方向偏移比例

float cameraY = 0f;//触摸点y轴方向偏移比例

private int colorBg;//背景色

private int shadeAlpha = 0xaa000000;//背景阴影透明度

private float touchProgress = 1f;//按压缩放动画控制

private float cameraProgress = 0f;//相机旋转(按压偏移)动画控制

TouchArea pressArea = new TouchArea(0,0,0,0);//按压效果区域

boolean isInPressArea = true;//按压位置是在内圈还是外圈

private int maxAngle = 5;//倾斜时的相机最大倾斜角度,deg

private float scale = 0.98f;//整体按压时的形变控制

private long pressTime = 0;//计算按压时间,小于500毫秒响应onClick()

Bitmap bitmap;//background为图片时

Rect srcRectF = new Rect();

RectF dstRectF = new RectF();

public PressFrameLayout(Context context) {

super(context);

}

public PressFrameLayout(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

}

public PressFrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

{

//取消硬件加速,否则低版本Android可能会绘制不了阴影

setLayerType(LAYER_TYPE_SOFTWARE, null);

//开启viewGroup的onDraw()

setWillNotDraw(false);

padding = DensityUtil.dip2px(getContext(),20);

cornerRadius = DensityUtil.dip2px(getContext(),5);

shadeOffset = DensityUtil.dip2px(getContext(),5);

//View的background为颜色或者图片的两种情况

Drawable background = getBackground();

if (background instanceof ColorDrawable) {

colorBg = ((ColorDrawable) background).getColor();

paintBg.setColor(colorBg);

} else {

bitmap = ((BitmapDrawable) background).getBitmap();

srcRectF = new Rect(0,0,bitmap.getWidth(),bitmap.getHeight());

}

setBackground(null);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (!isInPressArea) {

camera.save();

//相机在控件中心上方,在x,y轴方向旋转,形成控件倾斜效果

camera.rotateX(maxAngle*cameraX*cameraProgress);

camera.rotateY(maxAngle*cameraY*cameraProgress);

canvas.translate(width/2f, height/2f);

camera.applyToCanvas(canvas);

//还原canvas坐标系

canvas.translate(-width/2f, -height/2f);

camera.restore();

}

//绘制阴影和背景

paintBg.setShadowLayer(shadeOffset*touchProgress,0,0,(colorBg & 0x00FFFFFF) | shadeAlpha);

if (bitmap!=null){

canvas.drawBitmap(bitmap,srcRectF,dstRectF,paintBg);

}else {

canvas.drawRoundRect(dstRectF

,cornerRadius,cornerRadius,paintBg);

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);

width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);

dstRectF.set(padding,padding,width-padding,height-padding);

//计算输入按压的内部范围,布局中心部分为内圈,其他为外圈

pressArea.set((width-2*padding)/4f + padding,(height-2*padding)/4f + padding

,width-(width-2*padding)/4f - padding,height-(width-2*padding)/4f - padding);

}

/**

* 判断是按压内圈还是外圈

* @return true:按压内圈;false:按压外圈

*/

private boolean isInPressArea(float x, float y){

return x > pressArea.getLeft() && x < pressArea.getRight()

&& y >pressArea.getTop() && y < pressArea.getBottom();

}

@Override

public boolean onTouchEvent(MotionEvent event) {

AnimatorSet animatorSet = new AnimatorSet();

int duration = 100;//按压动画时长

int type = 0;

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

pressTime = System.currentTimeMillis();

type = 1;

isInPressArea = isInPressArea(event.getX(),event.getY());

break;

case MotionEvent.ACTION_CANCEL:

type = 2;

break;

case MotionEvent.ACTION_UP:

if((System.currentTimeMillis()-pressTime) < 500){

performClick();

}

type = 2;

break;

}

if (isInPressArea){//内圈按压效果

if (type !=0){

ObjectAnimator animX = ObjectAnimator.ofFloat(this,"scaleX"

,type==1?1:scale,type==1?scale:1).setDuration(duration);

ObjectAnimator animY = ObjectAnimator.ofFloat(this,"scaleY"

,type==1?1:scale,type==1?scale:1).setDuration(duration);

ObjectAnimator animZ = ObjectAnimator.ofFloat(this,"touchProgress"

,type==1?1:0,type==1?0:1).setDuration(duration);

animX.setInterpolator(new DecelerateInterpolator());

animY.setInterpolator(new DecelerateInterpolator());

animZ.setInterpolator(new DecelerateInterpolator());

animatorSet.playTogether(animX,animY,animZ);

animatorSet.start();

}

}else {//外圈按压效果

cameraX = (event.getX() - width / 2f) / ((width-2*padding)/2f);

if (cameraX > 1) cameraX = 1;

if (cameraX < -1) cameraX = -1;

cameraY = (event.getY() - height / 2f) / ((height-2*padding)/2f);

if (cameraY > 1) cameraY = 1;

if (cameraY < -1) cameraY = -1;

//坐标系调整

float tmp = cameraX;

cameraX = -cameraY;

cameraY = tmp;

switch (type) {

case 1://按下动画

ObjectAnimator.ofFloat(this,"cameraProgress"

,0,1).setDuration(duration).start();

break;

case 2://还原动画

ObjectAnimator.ofFloat(this,"cameraProgress"

,1,0).setDuration(duration).start();

break;

default:

break;

}

invalidate();

}

return true;

}

public float getTouchProgress() {

return touchProgress;

}

public void setTouchProgress(float touchProgress) {

this.touchProgress = touchProgress;

invalidate();

}

public float getCameraProgress() {

return cameraProgress;

}

public void setCameraProgress(float cameraProgress) {

this.cameraProgress = cameraProgress;

invalidate();

}

@Override

public boolean performClick() {

return super.performClick();

}

}

测试的xml布局

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="200dp"

android:layout_height="200dp"

android:background="#1493cd"

android:layout_centerInParent="true">

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/tv_test"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

android:textColor="@color/white"

android:textSize="48sp"

android:text="测试"/>

其他代码

TouchArea类:触摸区域

public class TouchArea {

private float left;

private float top;

private float right;

private float bottom;

TouchArea(float left, float top, float right, float bottom){

this.left = left;

this.top = top;

this.right = right;

this.bottom = bottom;

}

public void set(float left,float top,float right,float bottom){

this.left = left;

this.top = top;

this.right = right;

this.bottom = bottom;

}

public float getLeft() {

return left;

}

public void setLeft(float left) {

this.left = left;

}

public float getTop() {

return top;

}

public void setTop(float top) {

this.top = top;

}

public float getRight() {

return right;

}

public void setRight(float right) {

this.right = right;

}

public float getBottom() {

return bottom;

}

public void setBottom(float bottom) {

this.bottom = bottom;

}

}

dp转px

public static int dip2px(Context con, float dpValue) {

float scale = con.getResources().getDisplayMetrics().density;

return (int)(dpValue * scale + 0.5F);

}

文末

感谢大家关注我,分享Android干货,交流Android技术。

对文章有何见解,或者有何技术问题,都可以在评论区一起留言讨论,我会虔诚为你解答。

Android架构师系统进阶学习路线、58万字学习笔记、教学视频免费分享地址:https://space.bilibili.com/544650554

5bec6f9f6737

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值