安卓java新拟态风格UI实现

2023-10-25 07:59
文章标签 java 实现 ui 风格 安卓 拟态

本文主要是介绍安卓java新拟态风格UI实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

继承drawable方式实现

如果有问题请多多指教

新2.0源码已更新,文章后面下载

文章最后由我提到的硬件加速问题,如不进行动态更新画布可绘制后转bitmap或在bitmap上绘制即可。(注意bitmap宽高,阴影是超出原有view大小绘制的)

更新了2.0版本在文章最后
更多玩法

Flat
Concave
Convex
Pressed
整体
点击UI互交

package com.android.view;import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;public class DrawableSpan extends Drawable {private float[] angle = new float[8];private Draw draw;private int width,height,color = 0xffeeeeee,color_right = 0xffeeeeee,color_bottom = 0xffeeeeee,shadowX = 40,shadowY = 40;private Paint paint,paint_right,paint_bottom;private Path path,path_right,path_bottom;private DrawableSpan.Style style = DrawableSpan.Style.CIRCLE;private DrawableSpan.Model model = DrawableSpan.Model.FLAT;public DrawableSpan(Style s , int w, int h) {init(s, w, h);}public DrawableSpan(int w, int h) {init(Style.CIRCLE, w, h);}public DrawableSpan(DrawableSpan d) {this(d, d.getStyle(), d.getModel());}public DrawableSpan(DrawableSpan d, Model m) {this(d, d.getStyle(), m);}public DrawableSpan(DrawableSpan d, Style s, Model m) {init(s, d.getIntrinsicWidth(), d.getIntrinsicHeight());int[] con = d.getContainerdeltaLength();setContainerdeltaLength(con[0], con[1]);setRound(d.getRound());setColor(d.getColor());setModel(m);setColorFilter(d.getColorFilter());setAlpha(d.getAlpha());}private void init(Style s, int w, int h) {style = s;width = w;height = h;super.setBounds(0, 0, w , h);paint = new Paint(ANTI_ALIAS_FLAG);paint_right = new Paint(ANTI_ALIAS_FLAG);paint_bottom = new Paint(ANTI_ALIAS_FLAG);path = new Path();path_right = new Path();path_bottom = new Path();}@Overridepublic void draw(Canvas c) {if (c.isHardwareAccelerated()) {Log.w("DrawableSpan$onDraw{Canvas}", "Accelerated by hardware!");}c.clipRect(-(shadowX + 10), -(shadowY + 10), width + shadowX + 10, height + shadowY + 10);if (model == Model.CONCAVE) {c.clipPath(path);c.drawPath(path, paint);c.drawPath(path_bottom, paint_bottom);c.drawPath(path_right, paint_right);} else {c.drawPath(path_bottom, paint_bottom);c.drawPath(path_right, paint_right);c.drawPath(path, paint);}try {if (draw != null) {draw.onDraw(this, c);}} catch (Exception e) {Log.w("DrawableSpan$onDraw{Canvas}", e.toString());}}public void setContainerdeltaLength(int x, int y) {shadowX = (int) (x / 1.25);shadowY = (int) (y / 1.25);}public void setContainerdeltaLength(int i) {setContainerdeltaLength(i, i);}public void setStyle(Style s) {if (s != null) {style = s;}}public void setModel(Model m) {if (m != null) {model = m;}}public void setColor(int i) {setColor(i, i);}public void setColor(int v, int i) {color = v;color_right = i;color_bottom = i;}public void setRound(float[] r) {if (r == null) {return;}switch (r.length) {case 1:for (int i=0;i < 8;i++) {angle[i] = r[0];}break;case 4:for (int i = 0,v = 0;i < 8;i++) {if (i % 2 != 0) {angle[i - 1] = r[v];angle[i] = r[v];v++;}}break;case 8:for (int i=0;i < 8;i++) {angle[i] = r[i];}break;default:for (int i=0;i < 8;i++) {if (i < r.length) {angle[i] = r[i];} else {angle[i] = 0;}}break;}}public DrawableSpan invalidate(int w, int h) {width = w;height = h;super.setBounds(0, 0, w , h);return invalidate();}public DrawableSpan invalidate() {postColor(color, color_right, color_bottom);postPath(width, height);super.invalidateSelf();return this;}public void setDraw(Draw d) {draw = d;}@Overridepublic void setAlpha(int i) {paint.setAlpha(i);paint_right.setAlpha(i);paint_bottom.setAlpha(i);}@Overridepublic void setColorFilter(ColorFilter f) {paint.setColorFilter(f);paint_right.setColorFilter(f);paint_bottom.setColorFilter(f);}public Model getModel() {return model;}public Style getStyle() {return style;}public int getColor() {return color;}public float[] getRound() {return angle;}public int[] getContainerdeltaLength() {return new int[]{(int)(shadowX * 1.25),(int)(shadowY * 1.25)};}@Override  public int getIntrinsicWidth() {  return super.getBounds().width() - shadowX;  }@Override  public int getIntrinsicHeight() {  return super.getBounds().height() - shadowY;  }@Overridepublic int getOpacity() {return PixelFormat.TRANSLUCENT;}public enum Style {RECTANGLE //矩形, CIRCLE;  //圆形}public enum Model {FLAT      //平, CONCAVE  //凹, CONVEX   //凸, PRESSED; //合并}public interface Draw {void onDraw(DrawableSpan drawable, Canvas canvas);}private void postColor(int i, int r, int b) {paint.setShader(null);paint_right.setShadowLayer(0, 0, 0, 0);paint_bottom.setShadowLayer(0, 0, 0, 0);int rc = manipulateColor(r, 1.1f);int bc = manipulateColor(b, 0.9f);paint.setColor(i);paint_right.setColor(rc);paint_bottom.setColor(bc);if (model == Model.CONCAVE || model == Model.PRESSED) {LinearGradient linearGradient = new LinearGradient(0f, 0f, width, height, bc, rc, Shader.TileMode.CLAMP);paint.setShader(linearGradient);} else if (model == Model.CONVEX) {LinearGradient linearGradient = new LinearGradient(0f, 0f, width, height, rc, bc, Shader.TileMode.CLAMP);paint.setShader(linearGradient);}if (model != Model.CONCAVE || model != Model.CONVEX) {paint_right.setShadowLayer(shadowX, -shadowX / 2, -shadowX / 2, rc);paint_bottom.setShadowLayer(shadowY, shadowY / 2, shadowY / 2, bc);}}private void postPath(int w, int h) {if (path_right.isInverseFillType()) {path_right.toggleInverseFillType();//反转剪切模式}if (path_bottom.isInverseFillType()) {path_bottom.toggleInverseFillType();}path.reset();path_right.reset();path_bottom.reset();if (style == Style.RECTANGLE) {path.addRoundRect(0, 0, w  , h , angle, Path.Direction.CW);path_right.addRoundRect(0, 0, w  , h , angle, Path.Direction.CCW);path_bottom.addRoundRect(0, 0, w  , h, angle, Path.Direction.CCW);} else {float radius = h < w ? h / 2 : w / 2;path.addCircle(w / 2, h / 2, radius, Path.Direction.CW);path_right.addCircle(w / 2, h / 2, radius, Path.Direction.CW);path_bottom.addCircle(w / 2, h / 2, radius, Path.Direction.CW);}path.close();path_right.close();path_bottom.close();if (model == Model.CONCAVE) {if (!path_right.isInverseFillType()) {path_right.toggleInverseFillType();}if (!path_bottom.isInverseFillType()) {path_bottom.toggleInverseFillType();}}}public static int manipulateColor(int color, float factor) {int a = Color.alpha(color);int r = Math.round(Color.red(color) * factor);int g = Math.round(Color.green(color) * factor);int b = Math.round(Color.blue(color) * factor);return Color.argb(a,Math.min(r, 255),Math.min(g, 255),Math.min(b, 255));}public static void invalidateView(final DrawableSpan d, final View v) {View view = (View) v.getParent();if (view instanceof ViewGroup) {((ViewGroup)view).setClipChildren(false);}view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);/**禁用硬件加速(必须);根布局设置ClipChildren=false(必须);如本体设置禁用硬件加速会导致ClipChildren失效所以把禁用硬件加速设置到它的父布局注意:如view重叠后阴影是不生效的两个必须的条件要满足如果需要叠加需要在它的后面放个父布局这个父布局的宽高一定要大于阴影辐射范围暂时以此为准**/v.post(new Runnable(){@Overridepublic void run() {v.setBackground(d.invalidate(v.getMeasuredWidth(), v.getMeasuredHeight()));}});}
}

创建方法:

DrawableSpan(宽,高)
–这里的宽高指初始化背景大小,后面可重新设置大小
DrawableSpan(图形样式Style,宽,高)
DrawableSpan(使用旧DrawableSpan参数,图形样式Style,阴影样式Model)
DrawableSpan(使用旧DrawableSpan参数,阴影样式Model)
DrawableSpan(使用旧DrawableSpan参数)

自定义方法void:

setContainerdeltaLength(X轴阴影范围,Y轴阴影范围)
setContainerdeltaLength(阴影范围)–也就是x和y一样
–可以通过它改变阴影范围实现UI动态互交

setStyle(DrawableSpan.Style)–背景样式
DrawableSpan.Style.RECTANGLE 矩形
DrawableSpan.Style.CIRCLE 圆形

setModel(DrawableSpan.Model)–阴影样式
DrawableSpan.Model.FLAT 中间平
DrawableSpan.Model.CONCAVE 中间凹
DrawableSpan.Model.CONVEX 中间凸
DrawableSpan.Model.PRESSED 合并 平和凹

setColor(Color)–背景色0x
setColor(Color,Color)–背景色,阴影色
–如白色不要直接0xffffffff这样阴影是显示不出来的
需要比0xffffffff暗一点即可,黑色同理

setRound(float[])–圆角(数组)
–设置1~8个角
–参数{10} 所有角都圆角10
–参数{10,0,10,0} 左上,右上,右下,左下
–参数{10,10,10,10,10,10,10,10}
参考path.addRoundRect

setDraw(DrawableSpan.Draw)
–设置画布回调,可在画布继续绘制

invalidate()–直接刷新画布
invalidate(宽,高)–更新画布宽高
上面那些set方法都不会主动更新画布
需要调用此方法更新
也可在id.setBackground(invalidate())

简单调用,简写方法

DrawableSpan.invalidateView(DrawableSpan,View)
第一个参数为创建好的DrawableSpan
第二参数为需要设置背景的view

需要注意:

—1.禁用硬件加速(必须)
—2.根布局设置ClipChildren=false(必须);

如本体设置禁用硬件加速会导致ClipChildren失效
所以把禁用硬件加速设置到它的父布局

注意:
如view重叠后阴影是不生效的
两个必须的条件要满足
如果需要叠加要在它的后面放个父布局(给它弄个爹)
这个父布局(爹)的宽高一定要大于阴影辐射范围

暂时以此为准

MainActivity

package com.android.view;import android.app.Activity;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.widget.LinearLayout;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.graphics.Paint;
import android.widget.Toast;
import android.view.MotionEvent;
import android.animation.ValueAnimator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.animation.Animator;public class MainActivity extends Activity {private int current;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);LinearLayout view = findViewById(R.id.view);LinearLayout view1 = findViewById(R.id.view1);TextView text = findViewById(R.id.text);TextView text1 = findViewById(R.id.text1);TextView text2 = findViewById(R.id.text2);TextView text3 = findViewById(R.id.text3);TextView text4 = findViewById(R.id.text4);DrawableSpan drawable = new DrawableSpan(DrawableSpan.Style.RECTANGLE, 30, 30);drawable.setRound(new float[]{30});drawable.setModel(DrawableSpan.Model.FLAT);drawable.setColor(0xFFECECEC);drawable.setContainerdeltaLength(20);DrawableSpan.invalidateView(drawable, view);final DrawableSpan drawable1 = new DrawableSpan(drawable, DrawableSpan.Style.CIRCLE, DrawableSpan.Model.FLAT);DrawableSpan.invalidateView(drawable1, text);DrawableSpan drawable2 = new DrawableSpan(drawable, DrawableSpan.Model.FLAT);DrawableSpan drawable3 = new DrawableSpan(drawable, DrawableSpan.Model.CONCAVE);DrawableSpan drawable4 = new DrawableSpan(drawable, DrawableSpan.Model.CONVEX);DrawableSpan drawable5 = new DrawableSpan(drawable, DrawableSpan.Model.PRESSED);DrawableSpan.invalidateView(drawable2, text1);DrawableSpan.invalidateView(drawable3, text2);DrawableSpan.invalidateView(drawable4, text3);DrawableSpan.invalidateView(drawable5, text4);final DrawableSpan.Model[] mode = new DrawableSpan.Model[]{DrawableSpan.Model.FLAT,DrawableSpan.Model.CONCAVE,DrawableSpan.Model.CONVEX,DrawableSpan.Model.PRESSED};current = 0;text.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {current = current < 3 ? current + 1 : 0;drawable1.setModel(mode[current]);drawable1.invalidate();Toast.makeText(MainActivity.this, "当前" + drawable1.getModel().toString(), Toast.LENGTH_SHORT).show();}});final DrawableSpan drawable6 = new DrawableSpan(drawable, DrawableSpan.Model.FLAT);DrawableSpan.invalidateView(drawable6, view1);final int[] length = drawable6.getContainerdeltaLength();final ValueAnimator valueAnimator = ValueAnimator.ofInt(length[0], 0);// 持续时间valueAnimator.setDuration(600);// 加速插值器valueAnimator.setInterpolator(new AccelerateInterpolator());valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int value = animation.getAnimatedValue();drawable6.setContainerdeltaLength(value);drawable6.invalidate();}});final ValueAnimator valueAnimator1 = ValueAnimator.ofInt(0, length[0]);// 持续时间valueAnimator1.setDuration(300);// 加速插值器valueAnimator1.setInterpolator(new AccelerateInterpolator());valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int value = animation.getAnimatedValue();drawable6.setContainerdeltaLength(value);drawable6.invalidate();}});valueAnimator.addListener(new Animator.AnimatorListener(){@Overridepublic void onAnimationStart(Animator p1) {}@Overridepublic void onAnimationEnd(Animator p1) {valueAnimator1.start();}@Overridepublic void onAnimationCancel(Animator p1) {}@Overridepublic void onAnimationRepeat(Animator p1) {}});view1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {valueAnimator.start();}});}
} 

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:clipChildren="false"android:background="#FFECECEC"android:orientation="vertical"><LinearLayoutandroid:layout_height="230dp"android:layout_width="230dp"android:orientation="vertical"android:id="@+id/view"android:gravity="center"><LinearLayoutandroid:layout_height="190dp"android:layout_width="190dp"android:orientation="vertical"android:gravity="center"><TextViewandroid:layout_width="150dp"android:layout_height="150dp"android:text="新拟态"android:id="@+id/text"android:gravity="center"/></LinearLayout></LinearLayout><LinearLayoutandroid:layout_height="130dp"android:layout_width="match_parent"android:orientation="horizontal"android:layout_marginTop="30dp"android:gravity="center"><TextViewandroid:layout_height="60dp"android:layout_width="60dp"android:text="Flat"android:gravity="center"android:id="@+id/text1"/><TextViewandroid:layout_height="60dp"android:layout_width="60dp"android:text="Concave"android:gravity="center"android:layout_marginLeft="25dp"android:id="@+id/text2"/><TextViewandroid:layout_height="60dp"android:layout_width="60dp"android:text="Convex"android:gravity="center"android:layout_marginLeft="25dp"android:id="@+id/text3"/><TextViewandroid:layout_height="60dp"android:layout_width="60dp"android:text="Pressed"android:gravity="center"android:layout_marginLeft="25dp"android:id="@+id/text4"/></LinearLayout><LinearLayoutandroid:layout_height="50dp"android:layout_width="100dp"android:orientation="vertical"android:layout_marginTop="20dp"android:id="@+id/view1"/></LinearLayout>

看完了不点赞关注?

捐赠:
捐赠后持续更新
支付宝:
点击跳转支付宝即可捐赠

新2.0已更新 源码蓝奏云下载
https://wwp.lanzoup.com/iKVLH02md0tc
1.自定义图形样式
2.图形叠加(多层阴影绘制)

更多功能

这篇关于安卓java新拟态风格UI实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/281125

相关文章

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick