安卓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

相关文章

springboot集成easypoi导出word换行处理过程

《springboot集成easypoi导出word换行处理过程》SpringBoot集成Easypoi导出Word时,换行符n失效显示为空格,解决方法包括生成段落或替换模板中n为回车,同时需确... 目录项目场景问题描述解决方案第一种:生成段落的方式第二种:替换模板的情况,换行符替换成回车总结项目场景s

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——