美文网首页
第一章:Android高级UI - UI原理与高级绘制(2)

第一章:Android高级UI - UI原理与高级绘制(2)

作者: IOXusu | 来源:发表于2020-07-07 08:10 被阅读0次

2.Paint/Cavans高级绘制

画笔,保存了绘制几何图形,文本和维度的样式和颜色信息

1.常用API

 private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.RED);//设置颜色
        mPaint.setARGB(255, 255, 255, 0);//设置Paint对象颜色,范围为0-255
        mPaint.setAlpha(200);//设置alpha不透明度,范围为0-255
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setStyle(Paint.Style.STROKE);//描边效果;
        mPaint.setStrokeWidth(3);//描边宽度
        mPaint.setStrokeJoin(Paint.Join.MITER);//拐角风格
        mPaint.setStrokeCap(Paint.Cap.ROUND);//圆角效果
        mPaint.setShader(new SweepGradient(200, 200, Color.BLUE, Color.BLACK));//设置环形渲染器
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));//设置图层混合模式
        mPaint.setColorFilter(new LightingColorFilter(0x00ffff, 0x00000));//设置颜色过滤器
        mPaint.setFilterBitmap(true);//设置双线性过滤
        mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.INNER));//设置画笔遮罩滤镜,传入度数和样式
        mPaint.setTextScaleX(2);//设置文本缩放倍数
        mPaint.setTextSize(23);//设置字体大小
        mPaint.setTextAlign(Paint.Align.CENTER);//设置文本对齐方式
        mPaint.setUnderlineText(true);//设置下划线
        String str = "Android开发";
        mPaint = new Paint();
        Rect rect = new Rect();
        mPaint.getTextBounds(str, 0, str.length(), rect);//测试文本大小,将文本大小信息放在rect中
        mPaint.measureText(str);//获取文本的宽
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获取字体度量对象
        float ascent = fontMetrics.ascent;
        Log.e(TAG, "ascent: "+ascent+"---"+
                "bottom: "+fontMetrics.bottom+"---"+
                "descent: "+fontMetrics.descent+"---" +
                "leading: "+fontMetrics.leading+"---" +
                "top: "+fontMetrics.top+"---" );


    }

以上这些是常用API,部分API的详细记录一下

(1)mPaint.setStyle(Paint.Style.STROKE);//描边效果;
这个主要有这几种效果 下图,,
STROKE :就是下图圆环的效果同时通过mPaint.setStrokeWidth(20)可以设置圆环的宽度;
FILL:就是中间那个圆效果实心圆;
FILL_AND_STROKE:这个看名称就懂了,就是这两个的合体吧所以和FILL效果一样

image.png

(2)setStrokeCap //圆角效果


image.png

(3)paint.setStrokeJoin(Paint.Join.MITER);


image.png

(4)setFilterBitmap(true)
设置了这个后为true后我们可以发现打了马赛克样式的图片看着背景变虚化了


image.png

(5)mPaint.getFontMetrics();//获取字体度量对象

String str = "Android开发";
mPaint = new Paint();
Rect rect = new Rect();
mPaint.getTextBounds(str, 0, str.length(), rect);//测试文本大小,将文本大小信息放在rect中
mPaint.measureText(str);//获取文本的宽
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();//获取字体度量对象
float ascent = fontMetrics.ascent;
Log.e(TAG, "ascent: "+ascent+"---"+
                "bottom: "+fontMetrics.bottom+"---"+
                "descent: "+fontMetrics.descent+"---" +
                "leading: "+fontMetrics.leading+"---" +
                "top: "+fontMetrics.top+"---" );
image.png image.png

2.Paint颜色相关
(1)setColor(int color)
(2)setARGB(int a,int r,int g,int b)
(3)setShader(Shader s)参数着色器对象,一般使用shader的几个子类
下面就是shader的几个子类详解
①LinearGradient线性渲染
构造方法:

 mLinearGradient = new LinearGradient(0, 0, 500, 500, new int[]{Color.GREEN, Color.RED}, new float[]{0.5f, 1}, Shader.TileMode.CLAMP);

参数比较多
(x0,y0,x1,y1)前四个参数就是两个端点的位置比如 (0,0) (500,500)就是屏幕左上角到(500,500)这个位置这两个点之间的渐变过程 我们想象画一个矩形或者圆形就明白了
int[] colors 渐变颜色值
float[] {0.5f,1} 表示颜色占据的渲染比例(可以为空)
效果图


image.png

②RadialGradient环形渲染

 /**
         * Create a shader that draws a radial gradient given the center and radius.
         *
         * @param centerX  辐射中心的X坐标
         * @param centerY  辐射中心的Y坐标
         * @param radius   辐射半径
         * @param colors   辐射渐变颜色数组
         * @param stops    辐射渐变位置数组
         * @param tileMode 辐射范围之外的着色模式 shader模式
         * @param centerColor 中心点渐变颜色
         * @param edgeColor   边界渐变颜色
         */
        mShader = new RadialGradient(250,250,250,
                new int[]{Color.RED,Color.GREEN}, null,Shader.TileMode.CLAMP);
        mPaint.setShader(mShader);![image.png](https://img.haomeiwen.com/i1906805/92c02061997639e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

        canvas.drawCircle(250, 250, 250, mPaint);

效果如下:


image.png

③扫描渲染

 /**
         * A Shader that draws a sweep gradient around a center point.
         *
         * @param cx       The x-coordinate of the center  扫描中心x轴
         * @param cy       The y-coordinate of the center  扫描中心位置y轴
         * @param color0   The color to use at the start of the sweep  扫描开始颜色
         * @param color1   The color to use at the end of the sweep  扫描终止颜色
         */
        mShader = new SweepGradient(250, 250, Color.RED, Color.GREEN);
        mPaint.setShader(mShader);
        canvas.drawCircle(250, 250, 250, mPaint);

效果图:


image.png

④位图渲染
原本是这样一张图


image.png
 /**
         * Call this to create a new shader that will draw with a bitmap.
         *
         * @param bitmap The bitmap to use inside the shader  这个不用说了吧
         * @param tileX The tiling mode for x to draw the bitmap in.  x轴方向的mode
         * @param tileY The tiling mode for y to draw the bitmap in.  y轴方向的mode
         */
        mShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
        mPaint.setShader(mShader);
        canvas.drawRect(0, 0, 5000, 5000, mPaint);

添加位图渲染后


image.png image.png

⑤组合渲染

 Shader mShaderA = new SweepGradient(250, 250, Color.RED, Color.GREEN);
 Shader mShaderB = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);
        /**
         * Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
         * When the mode is applied, it will be given the result from shader A as its
         * "dst", and the result from shader B as its "src".
         *
         * @param shaderA  
         * @param shaderB  
         * @param mode     组合两种shader的颜色模式
         * @Parm   Xfermode  组合两种shader的颜色模式
         * ComposeShader有两种构造方法  Xfermode后面说
         */
       // mShader = new ComposeShader( Shader shaderA,  Shader shaderB,  Xfermode mode);
        mShader = new ComposeShader(mShaderA, mShaderB, PorterDuff.Mode.ADD);
        mPaint.setShader(mShader);
        canvas.drawRect(0, 0, 5000, 5000, mPaint);

这里我就把上面的扫描渲染和位图渲染组合在了一起 效果图如下:


image.png

3.lPorterDuff.Mode图层混合模式
先绘制的是dest目标图像 后绘制的是src源图像

//所绘制不会提交到画布上
new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
//显示上层绘制的图像
new PorterDuffXfermode(PorterDuff.Mode.SRC),
//显示下层绘制图像
new PorterDuffXfermode(PorterDuff.Mode.DST),
//正常绘制显示,上下层绘制叠盖
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),

//上下层都显示,下层居上显示
new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
//取两层绘制交集,显示上层
new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
//取两层绘制交集,显示下层
new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
//取上层绘制非交集部分,交集部分变成透明
new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),

//取下层绘制非交集部分,交集部分变成透明
new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
//取上层交集部分与下层非交集部分
new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
//取下层交集部分与上层非交集部分
new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
//去除两图层交集部分
new PorterDuffXfermode(PorterDuff.Mode.XOR),

//取两图层全部区域,交集部分颜色加深
new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
//取两图层全部区域,交集部分颜色点亮
new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
//取两图层交集部分,颜色叠加
new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
//取两图层全部区域,交集部分滤色
new PorterDuffXfermode(PorterDuff.Mode.SCREEN),

//取两图层全部区域,交集部分饱和度相加
new PorterDuffXfermode(PorterDuff.Mode.ADD),
//取两图层全部区域,交集部分叠加
new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
image.png

4.效果相关


image.png
image.png
image.png

4.Canvas高阶使用技巧-变换,状态保存,离屏缓冲,粒子特效


image.png
image.png image.png

项目实战-爆炸粒子效果

public class ParticleView extends View {

    private Paint mPaint;
    private Bitmap mBitmap;
    private float d = 30;
    private ValueAnimator mValueAnimator;
    private List<Ball> mBalls = new ArrayList<>();

    public ParticleView(Context context) {
        this(context, null);
    }

    public ParticleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ParticleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaint = new Paint();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.head, options);
        for (int i = 0; i < mBitmap.getWidth(); i++) {
            for (int j = 0; j < mBitmap.getHeight(); j++) {
                Ball ball = new Ball();
                ball.color = mBitmap.getPixel(i, j);
                ball.x = i * d + d / 2;
                ball.y = j * d + d / 2;
                ball.r = d / 2;


                ball.vx = (float) (-20 + Math.random() * 40);
                ball.vy = (float) (-15 + Math.random() * 50);
                mBalls.add(ball);
            }
        }
        mValueAnimator = ValueAnimator.ofFloat(0, 1);
        mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mValueAnimator.setDuration(1000);
        mValueAnimator.setInterpolator(new LinearInterpolator());
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                updateBall();
                invalidate();
            }
        });
    }

    private void updateBall() {
        for (Ball mBall : mBalls) {
            mBall.x += mBall.vx;
            mBall.y += mBall.vy;

            mBall.vx += mBall.ax;
            mBall.vy += mBall.ay;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(200, 200);
        for (Ball mBall : mBalls) {
            mPaint.setColor(mBall.color);
            canvas.drawCircle(mBall.x, mBall.y, mBall.r, mPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            mValueAnimator.start();
        }
        return true;
    }
}

项目实战-开屏动

public class SplashView extends View {

    //旋转圆的画笔
    private Paint mPaint;
    //扩散圆的画笔
    private Paint mHolePaint;

    //背景色
    private int mBackgroundColor = Color.WHITE;
    private int[] mCircleColors;

    //圆心坐标
    private float mCenterX;
    private float mCenterY;
    private float mDistance;

    //6个小球的半径
    private float mCircleRadius = 18;
    //旋转圆半径
    private float mRotateRadius = 90;
    //扩散圆半径
    private float mHoleRadius;

    private ValueAnimator mValueAnimator;

    private SplashState mState;

    private float mCurrentRotateAngle;
    private float mCurrentRotateRadius = 90;
    private float mCurrentHoleRadius;

    public SplashView(Context context) {
        this(context, null);
    }

    public SplashView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHolePaint.setStyle(Paint.Style.STROKE);
        mHolePaint.setColor(mBackgroundColor);

        mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w * 1f / 2;
        mCenterY = h * 1f / 2;
        mDistance = (float) Math.hypot(w, h) / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mState == null) {
            mState = new RotateState();
        }
        mState.drawState(canvas);
    }

    private abstract static class SplashState {
        abstract void drawState(Canvas canvas);
    }

    //1.旋转
    private class RotateState extends SplashState {

        private RotateState() {
            mValueAnimator = ValueAnimator.ofFloat(0, (float) (Math.PI * 2));
            mValueAnimator.setRepeatCount(2);
            mValueAnimator.setDuration(1200);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRotateAngle = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mState = new MerginState();
                }
            });
            mValueAnimator.start();
        }

        @Override
        void drawState(Canvas canvas) {
            //绘制背景
            drawBackground(canvas);
            //绘制小球
            drawCircles(canvas);
        }
    }

    private void drawCircles(Canvas canvas) {
        float rotateAngle = (float) (Math.PI * 2 / mCircleColors.length);
        for (int i = 0; i < mCircleColors.length; i++) {
            float angle = i * rotateAngle + mCurrentRotateAngle;
            float cx = (float) (Math.cos(angle) * mCurrentRotateRadius + mCenterX);
            float cy = (float) (Math.sin(angle) * mCurrentRotateRadius + mCenterY);
            mPaint.setColor(mCircleColors[i]);
            canvas.drawCircle(cx, cy, mCircleRadius, mPaint);
        }
    }

    private void drawBackground(Canvas canvas) {
        if (mCurrentRotateRadius > 0) {
            float strokeWidth = mDistance - mCurrentHoleRadius;
            float radius = strokeWidth / 2 + mCurrentHoleRadius;
            mHolePaint.setStrokeWidth(strokeWidth);
            canvas.drawCircle(mCenterX, mCenterY, radius, mHolePaint);
        } else {
            canvas.drawColor(mBackgroundColor);
        }
    }

    //2.扩散聚合
    private class MerginState extends SplashState {

        private MerginState() {
            mValueAnimator = ValueAnimator.ofFloat(mCircleRadius, mRotateRadius);
            mValueAnimator.setDuration(1200);
            mValueAnimator.setInterpolator(new OvershootInterpolator(10f));
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRotateRadius = (float) mValueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mState = new ExpandState();
                }
            });
            mValueAnimator.reverse();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);
            drawCircles(canvas);
        }
    }
    //3.水波纹

    private class ExpandState extends SplashState {

        public ExpandState() {
            mValueAnimator = ValueAnimator.ofFloat(mCircleRadius, mDistance);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.setDuration(1200);
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentHoleRadius = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            mValueAnimator.start();
        }

        @Override
        void drawState(Canvas canvas) {
            drawBackground(canvas);
        }
    }

}

5.Path
直线

Path path = new Path();
path.moveTo(100,200);
path.lineTo(140,250);
//path.rLineTo(40,50);

添加路径

Path newPath = new Path();
newPath.moveTo(200,200);
newPath.lineTo(300,500);
path.addPath(newPath);

圆角矩形路径

RectF rectF = new RectF(200, 200, 500, 500);
path.addRoundRect(rectF,20,20, Path.Direction.CW);

二阶贝塞尔曲线

path.moveTo(300, 500);
path.quadTo(500, 100, 800, 500);

三阶贝塞尔曲线

path.moveTo(300, 500);
path.cubicTo(500, 100, 600, 1200, 800, 500);

6.PathMeasure

Path path = new Path();
path.lineTo(0, 200);
path.lineTo(200, 200);
path.lineTo(200, 0);
PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(path, false);
path.lineTo(0,0);
pathMeasure.setPath(path, false);
Log.e("TAG_", "onDraw: " + pathMeasure.getLength());
> onDraw: 800.0
image.png
image.png

pathMeasure.getSegment(200, 300, dst, true);最后一个参数为true的时候截取得片段不会发生变化,为false会将片段连起来,形态会发生变化


image.png image.png

小案例:


image.png
image.png image.png

3.事件传递机制

4.属性动画

相关文章

网友评论

      本文标题:第一章:Android高级UI - UI原理与高级绘制(2)

      本文链接:https://www.haomeiwen.com/subject/ujltqktx.html