android触摸效果,Android开发进阶:仿MIUI12控件触摸反馈效果(下沉+倾斜)附源码...
简单模仿了下MIUI12里控件的触摸反馈效果,转载请标明出处
效果简述
按压控件内圈区域,控件整体缩小,高度降低(阴影消失)
按压内圈
按压控件外圈区域,依据触摸点控件以中心为支点,向触摸点倾斜
按压外圈
实现原理
按压内圈效果
内圈效果包含控件整体缩放、外圈阴影变化。
整体缩放:给控件的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
android触摸效果,Android开发进阶:仿MIUI12控件触摸反馈效果(下沉+倾斜)附源码...相关推荐
- 仿酷狗音乐播放器开发日志二十二 动态调色板控件第二版(性能大幅提升附源码)...
转载请说明原出处,谢谢~~ 在上次写的博客<仿酷狗音乐播放器开发日志二十一 开发动态调色板控件(附源码)>发布后,我在群里和网友讨论这个控件的性能和优 缺点,发现了他很多不足,还有很多提升 ...
- 【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
需要全部代码请点赞关注收藏后评论区留言私信~~~ 手机最开始用于通话,后来增加了短信功能,初步满足了人与人之间的沟通需求.然而短信只能发文字,于是出现了能够发图片的彩信,但不管短信还是彩信,资费都太贵 ...
- 【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
需要全部代码请点赞关注收藏后评论区留言私信~~~ 与传统的影视行业相比,诞生于移动互联网时代的短视频是个全新行业,它制作方便又容易传播,一出现就成为大街小巷的时髦潮流. 各行各业的人们均可通过短视频展 ...
- 【Android App】实战项目之仿微信的私信和群聊App(附源码)
手机最开始用于通话,后来增加了短信功能,初步满足了人与人之间的沟通需求.然而短信只能发文字,于是出现了能够发图片的彩信,但不管短信还是彩信,资费都太贵了,令人惜墨如金. 后来移动公司推出飞信,它支持从 ...
- C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码...
原文:C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码 前言 系列目录 C#使用Xamarin开发可移植移动应用目录 源码地址:https://github. ...
- C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码...
C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码 原文:C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码 前言 系列 ...
- C#使用Xamarin开发可移植移动应用进阶篇(8.打包生成安卓APK并精简大小),附源码
我记得,之前在写安卓方面的文章的时候,有人就问过我.Xamarin.Android为什么打包出来这么大?随便一个HelloWord就20-30MB? 嗯..今天我们就来解决这个问题.. 我们先从指定一 ...
- C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码
本篇..基本可以算是Xamarin在应用开发过程中的核心了..真的很很很重要.. 想学习的..想用的..建议仔细阅读..嗯..打酱油的 ..快速滑倒下面点个推荐 - - 哈哈哈... 今天的学习内容? ...
- Adroid游戏开发实例讲解(五)-哄娃神器之随机五彩泡(附源码)
Adroid游戏开发实例讲解(五)-哄娃神器之随机五彩泡(附源码) 程序之美 泡泡 一直被孩子认为是神奇的东西.记得儿时,经常 趁大人不注意,偷偷抓一把洗衣粉撞到口袋里,然后自己悄悄的找来一个小瓶子, ...
最新文章
- 浅谈Java的输入输出流
- nginx rewrite规则和参考
- NgModule imports定义的运行时数据结构
- c语言有一个已经排好的数组,C语言有一个已经排好序的数组。现输入一个数,要求按原来的规律将它插入数组中...
- DQL、DML、DDL、DCL的概念与区别
- java参数传入泛型类型_Java泛型参数界定到任何一个类型的范围
- python3.7怎么安装tensorflow_gpu_(更新版)python3.7 Windows10 tensorflow-GPU 安装
- python培训班靠谱吗-Python培训机构就业靠不靠谱?
- 软件需求工程与UML建模——第九组第六周工作总结
- DataGridView添加右键菜单等技巧
- linux内存寻址解析 (一)
- 《CSS权威指南》读书笔记
- 【商品架构day4】十年前淘宝商品系统怎么做平台化
- 600度近视眼恢复方法_600度的近视眼,恢复视力要注意
- 【信号与系统】Multisim 仿真抽样定理与信号恢复
- qt中采用G.729A进行网络语音通话实验程序
- 2016用户体验行业调查报告
- 对1bit的脉冲信号进行展宽,转为32bit位宽,并产生有效信号
- Android UI 之一步步教你自定义控件(自定义属性、合理设计onMeasure、合理设计onDraw等)
- jstl依赖_[JSTL表达式]