安卓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();

	}
	@Override
	public 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;
	}
	@Override
	public void setAlpha(int i) {
		paint.setAlpha(i);
		paint_right.setAlpha(i);
		paint_bottom.setAlpha(i);
	}
	@Override
	public 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;  
	}
	@Override
	public 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(){
				@Override
				public 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;
	@Override
    protected 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() {
				@Override
				public 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() {
				@Override
				public 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() {
				@Override
				public void onAnimationUpdate(ValueAnimator animation) {
					int value = animation.getAnimatedValue();
					drawable6.setContainerdeltaLength(value);
					drawable6.invalidate();
				}
			});
		valueAnimator.addListener(new Animator.AnimatorListener(){
				@Override
				public void onAnimationStart(Animator p1) {
				}
				@Override
				public void onAnimationEnd(Animator p1) {
					valueAnimator1.start();
				}
				@Override
				public void onAnimationCancel(Animator p1) {
				}
				@Override
				public void onAnimationRepeat(Animator p1) {
				}
			});

		view1.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View v) {
					valueAnimator.start();
					}
			});
	}
} 

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns: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">

	<LinearLayout
		android:layout_height="230dp"
		android:layout_width="230dp"
		android:orientation="vertical"
		android:id="@+id/view"
		android:gravity="center">

		<LinearLayout
			android:layout_height="190dp"
			android:layout_width="190dp"
			android:orientation="vertical"
			android:gravity="center">

			<TextView
				android:layout_width="150dp"
				android:layout_height="150dp"
				android:text="新拟态"
				android:id="@+id/text"
				android:gravity="center"/>

		</LinearLayout>

	</LinearLayout>

	<LinearLayout
		android:layout_height="130dp"
		android:layout_width="match_parent"
		android:orientation="horizontal"
		android:layout_marginTop="30dp"
		android:gravity="center">

		<TextView
			android:layout_height="60dp"
			android:layout_width="60dp"
			android:text="Flat"
			android:gravity="center"
			android:id="@+id/text1"/>

		<TextView
			android:layout_height="60dp"
			android:layout_width="60dp"
			android:text="Concave"
			android:gravity="center"
			android:layout_marginLeft="25dp"
			android:id="@+id/text2"/>

		<TextView
			android:layout_height="60dp"
			android:layout_width="60dp"
			android:text="Convex"
			android:gravity="center"
			android:layout_marginLeft="25dp"
			android:id="@+id/text3"/>

		<TextView
			android:layout_height="60dp"
			android:layout_width="60dp"
			android:text="Pressed"
			android:gravity="center"
			android:layout_marginLeft="25dp"
			android:id="@+id/text4"/>

	</LinearLayout>

	<LinearLayout
		android: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.图形叠加(多层阴影绘制)

更多功能

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桔.com

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值