IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 圆角、渐变色、阴影布局 -> 正文阅读

[游戏开发]圆角、渐变色、阴影布局

对于圆角、渐变色、阴影布局这些元素,第一想法是使用创建一个shape来实现,但是不便于管理,最重要的是取名还困难,所以就冒出了一个使用布局来实现的想法。

PS:先附上该自定义控件实现后的效果图,所看到的太阳、地球、月亮、星星,都是用该控件实现的
所看到的太阳、地球、月亮、星星,都是用该控件实现的

控件代码:此处是继承自布局,对于不想要套一层布局的需求,可以继承自己想要的控件,然后把public void dispatchDraw(Canvas canvas);方法替换成public void onDraw(Canvas canvas);方法,记得把super(Canvas canvas);放到方法的最后,不然内容会被遮挡。


public class StyleLayout extends ConstraintLayout {
    private StyleViewAttr styleViewAttr;

    private Path shadowPath;//阴影路径
    private RectF backgroundRectF;//背景范围,用于计算背景路径
    private Path backgroundPath;//背景路径,包含绘制标准,即相对这条路径来计算绘制范围
    private RectF borderRectF;//边框范围,用于计算边框路径
    private Path borderPath;//边框路径
    private Shader backgroundGradient;//渐变背景色对象
    private Paint mPaint;//画笔

    private float shadowRadioArr[] = new float[8];
    private float radioArr[] = new float[8];
    public StyleLayout(Context context) {
        super(context);
        init(context, null);
    }

    public StyleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public StyleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    public void setStyleViewAttr(StyleViewAttr styleViewAttr) {
        this.styleViewAttr = styleViewAttr;
        //圆角,参考Path.addRoundRect()的api说明, clearBurr是用于去掉阴影是圆角的时候的毛边
        int clearBurr = 6;
        float roundAttrArr[] = new float[]{styleViewAttr.left_top_round, styleViewAttr.right_top_round, styleViewAttr.right_bottom_round, styleViewAttr.left_bottom_round};
        for(int i = 0; i < shadowRadioArr.length; i += 2){
            float roundValue = roundAttrArr[i / 2];
            shadowRadioArr[i] = roundValue + clearBurr;
            shadowRadioArr[i + 1] = roundValue + clearBurr;

            radioArr[i] = roundValue;
            radioArr[i + 1] = roundValue;
        }
    }

    private void init(Context context, AttributeSet attrs){
        shadowPath = new Path();
        backgroundPath = new Path();
        backgroundRectF = new RectF();
        borderPath = new Path();
        borderRectF = new RectF();
        mPaint = new Paint();
        styleViewAttr = new StyleViewAttr(context, attrs);
        setStyleViewAttr(styleViewAttr);

        //去掉阴影和边框占据的空间
        int backgroundStartX = (int) (styleViewAttr.shadowWidth - getShadowOffsetWidth());
        int backgroundStartY = (int) (styleViewAttr.shadowWidth - getShadowOffsetHeight());
        setPadding(backgroundStartX + getPaddingStart(), backgroundStartY + getPaddingTop(), backgroundStartX + getPaddingEnd(), backgroundStartY + getPaddingBottom());
    }


    @Override
    public void dispatchDraw(Canvas canvas) {
        //抗锯齿
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));

        //布局没有变动的时候,不需要重新计算
        if(getWidth() != backgroundRectF.right + styleViewAttr.borderWidth + styleViewAttr.shadowWidth
                || getWidth() != backgroundRectF.bottom + styleViewAttr.borderWidth + styleViewAttr.shadowWidth) {
            computerRectF();
            //清除已添加的路径
            backgroundPath.rewind();
            shadowPath.rewind();
            borderPath.rewind();
        }

        //绘制阴影
        drawShadow(canvas);
        //绘制背景色
        drawBackgroundGradient(canvas);
        //绘制边框线
        drawBorder(canvas);

        super.dispatchDraw(canvas);
    }

    private void computerRectF(){
        //空出阴影的绘制范围,加入了阴影的偏移量计算,当偏移量等于阴影宽度,背景色不会被遮挡
        float backgroundStartX = styleViewAttr.shadowWidth - getShadowOffsetWidth();
        float backgroundStartY = styleViewAttr.shadowWidth - getShadowOffsetHeight();
        float backgroundWidth = getWidth() - styleViewAttr.shadowWidth * 2;
        float backgroundHeight = getHeight() - styleViewAttr.shadowWidth * 2;
        //设置背景色的绘制范围
        backgroundRectF.set(backgroundStartX, backgroundStartY, backgroundStartX + backgroundWidth, backgroundHeight + backgroundStartY);

        //创建背景色
        backgroundGradient = new LinearGradient(getGradientStartX(), //起始位置的X坐标
                getGradientStartY(), //起始位置的Y坐标
                getGradientEndX(), //终止位置的X坐标
                getGradientEndY(),//终止位置的Y坐标
                styleViewAttr.bgGradientColor,//渐变颜色数组,根据数组的顺序进行渲染
                styleViewAttr.bgcGradientWeight,//比重,设置每种颜色在总宽度中的比重,不设置则每种颜色的比重相等
                Shader.TileMode.CLAMP);//设置平铺模式
        //在空出阴影的基础上,再空出边框的绘制范围
        float borderStartX = backgroundStartX + styleViewAttr.borderWidth / 2f;
        float borderStartY = backgroundStartY + styleViewAttr.borderWidth / 2f;
        float borderWidth = backgroundWidth - styleViewAttr.borderWidth;
        float borderHeight = backgroundHeight - styleViewAttr.borderWidth;
        //设置边框的绘制范围
        borderRectF.set(borderStartX, borderStartY, borderStartX + borderWidth, borderStartY + borderHeight);

    }

    private float getShadowOffsetWidth(){
        return styleViewAttr.shadowOffsetX * styleViewAttr.shadowWidth;
    }

    private float getShadowOffsetHeight(){
        return styleViewAttr.shadowOffsetY * styleViewAttr.shadowWidth;
    }


    /**绘制阴影
     * @param canvas
     */
    public void drawShadow(Canvas canvas){
        if(styleViewAttr.shadowWidth > 0) {
            //不使用shadowPath进行绘制的原因,是因为圆角会有黑色的毛边
            if(shadowPath.isEmpty()) {
                shadowPath.addRoundRect(backgroundRectF, shadowRadioArr, Path.Direction.CW);
            }
            mPaint.reset();
            mPaint.setShadowLayer(styleViewAttr.shadowWidth, getShadowOffsetWidth(), getShadowOffsetHeight(), styleViewAttr.shadowColor);
            mPaint.setStyle(Paint.Style.FILL);
            canvas.drawPath(shadowPath, mPaint);
        }
    }

    /**绘制渐变色背景
     *
     */
    private void drawBackgroundGradient(Canvas canvas){
        if(backgroundPath.isEmpty()) {
            backgroundPath.addRoundRect(backgroundRectF, radioArr, Path.Direction.CW);
        }

        mPaint.reset();
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setShader(backgroundGradient);
        canvas.drawPath(backgroundPath, mPaint);
    }

    /**绘制边框
     * @param canvas
     */
    private void drawBorder(Canvas canvas) {
        if(styleViewAttr.borderWidth > 0) {
            //不使用backgroundPath进行绘制的原因,是因为描边是沿着路径的两侧绘制的
            if(borderPath.isEmpty()) {
                borderPath.addRoundRect(borderRectF, radioArr, Path.Direction.CW);
            }

            mPaint.reset();
            mPaint.setColor(styleViewAttr.borderColor);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(styleViewAttr.borderWidth);
            canvas.drawPath(borderPath, mPaint);
        }
    }

    private float getGradientStartX(){
        if(styleViewAttr.bgcGradientAngle <= 45){
            return backgroundRectF.left;
        }else if(styleViewAttr.bgcGradientAngle < 90){
            return backgroundRectF.centerX() - getOtherSideLength(backgroundRectF.centerY());
        }else {
            return backgroundRectF.centerX();
        }
    }

    private float getGradientStartY(){
        if(styleViewAttr.bgcGradientAngle <= 45){
            return backgroundRectF.centerY() - getOtherSideLength(backgroundRectF.centerX());
        }else if(styleViewAttr.bgcGradientAngle < 90){
            return backgroundRectF.top;
        }else {
            return backgroundRectF.top;
        }
    }

    private float getGradientEndX(){
        if(styleViewAttr.bgcGradientAngle <= 45){
            return backgroundRectF.right;
        }else if(styleViewAttr.bgcGradientAngle < 90){
            return backgroundRectF.centerX() + getOtherSideLength(backgroundRectF.centerY());
        }else {
            return backgroundRectF.centerX();
        }
    }

    private float getGradientEndY(){
        if(styleViewAttr.bgcGradientAngle <= 45){
            return backgroundRectF.centerY() + getOtherSideLength(backgroundRectF.centerX());
        }else if(styleViewAttr.bgcGradientAngle < 90){
            return backgroundRectF.bottom;
        }else {
            return backgroundRectF.bottom;
        }
    }

    /**以背景色范围的中心,计算x轴半径与y轴半径的比值,
     * 当一条直角边固定的情况下,根据角度计算另一条直角边的长度
     * @return
     */
    private float getOtherSideLength(float fixedSide){
        //两条直角边的比例
        float tan = (float) Math.tan(Math.PI * styleViewAttr.bgcGradientAngle / 180);
        //计算正切值
        float scale = backgroundRectF.centerY() / backgroundRectF.centerX();
        //返回另一条直角边的长度
        return tan * fixedSide * scale;
    }

    public static class StyleViewAttr {
        public static final int SHADOW_LEFT_OFFSET = -1;//阴影偏移到左边
        public static final int SHADOW_VERTICAL_MIDDLE= 0;//阴影不偏移
        public static final int SHADOW_RIGHT_OFFSET= 1;//阴影偏移到右边
        public static final int SHADOW_TOP_OFFSET= -1;//阴影偏移到上方
        public static final int SHADOW_HORIZONTAL_MIDDLE = 0;//阴影不偏移
        public static final int SHADOW_BOTTOM_OFFSET= 1;//阴影偏移到下方

        /**圆角位置*/
        private float left_top_round;//左上角
        private float left_bottom_round;//左下角
        private float right_top_round;//右上角
        private float right_bottom_round;//右下角

        /**背景渐变色,可设置多个颜色,根据设置的方向自动渐变,
         * xml中使用bgGradientStartColor、bgGradientEndColor设置起始和结束颜色,不支持传递数组*/
        private int[] bgGradientColor;

        /**背景渐变色方向 角度[0°-90°]*/
        private float bgcGradientAngle;

        /**每种渐变色在整个控件中的比重*/
        private float[] bgcGradientWeight;

        /**边框宽度*/
        private int borderWidth;

        /**边框颜色*/
        private int borderColor;

        /**阴影颜色 */
        private int shadowColor;

        /**阴影宽度*/
        private int shadowWidth;

        /**阴影水平偏移距离,当偏移量等于阴影宽度,背景色不会被遮挡*/
        private int shadowOffsetX;

        /**阴影垂直偏移距离,当偏移量等于阴影宽度,背景色不会被遮挡*/
        private int shadowOffsetY;

        public StyleViewAttr(Context context, AttributeSet attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StyleLayout);
            int all_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_all_round, 0);
            left_top_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_left_top_round, all_round);
            left_bottom_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_left_bottom_round, all_round);
            right_top_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_right_top_round, all_round);
            right_bottom_round = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_right_bottom_round, all_round);
            int startColor = typedArray.getColor(R.styleable.StyleLayout_bgGradientStartColor, 0);
            int endColor = typedArray.getColor(R.styleable.StyleLayout_bgGradientEndColor, 0);
            float startWeight = typedArray.getFloat(R.styleable.StyleLayout_bgGradientStartWeight, 0f);
            float endWeight = typedArray.getFloat(R.styleable.StyleLayout_bgGradientEndWeight, 1f);
            borderWidth = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_borderWidth, 0);
            borderColor = typedArray.getColor(R.styleable.StyleLayout_borderColor, Color.TRANSPARENT);
            bgcGradientAngle = typedArray.getFloat(R.styleable.StyleLayout_bgcGradientAngle, 0);
            shadowColor = typedArray.getColor(R.styleable.StyleLayout_shadowColor, Color.GRAY);
            shadowWidth = typedArray.getDimensionPixelSize(R.styleable.StyleLayout_shadowWidth, 0);
            shadowOffsetX = typedArray.getInt(R.styleable.StyleLayout_shadowOffsetX, SHADOW_VERTICAL_MIDDLE);
            shadowOffsetY = typedArray.getInt(R.styleable.StyleLayout_shadowOffsetY, SHADOW_HORIZONTAL_MIDDLE);
            typedArray.recycle();

            if(startColor != 0 && endColor != 0){
                bgGradientColor = new int[]{startColor, endColor};
            }else if(startColor != 0){
                bgGradientColor = new int[]{startColor, startColor};
            }else {
                bgGradientColor = new int[]{endColor, endColor};
            }

            bgcGradientWeight = new float[]{startWeight, endWeight};

            shadowWidth = Math.max(shadowWidth, 0);
        }

        public StyleViewAttr() {

        }

        public StyleViewAttr setLeft_top_round(float left_top_round) {
            this.left_top_round = left_top_round;
            return this;
        }

        public StyleViewAttr setLeft_bottom_round(float left_bottom_round) {
            this.left_bottom_round = left_bottom_round;
            return this;
        }

        public StyleViewAttr setRight_top_round(float right_top_round) {
            this.right_top_round = right_top_round;
            return this;
        }

        public StyleViewAttr setRight_bottom_round(float right_bottom_round) {
            this.right_bottom_round = right_bottom_round;
            return this;
        }

        public StyleViewAttr setAllRound(float round){
            if(left_top_round == 0){
                left_top_round = round;
            }

            if(right_top_round == 0){
                right_top_round = round;
            }

            if(left_bottom_round == 0){
                left_bottom_round = round;
            }

            if(right_bottom_round == 0){
                right_bottom_round = round;
            }
            return this;
        }

        public StyleViewAttr setBgGradientColor(int...bgGradientColor) {
            if(bgGradientColor == null || bgGradientColor.length == 0){
                this.bgGradientColor = null;
            }else if(bgGradientColor.length == 1){
                this.bgGradientColor = new int[]{bgGradientColor[0], bgGradientColor[0]};
            }else {
                this.bgGradientColor = new int[bgGradientColor.length];
                System.arraycopy(bgGradientColor, 0, this.bgGradientColor, 0, bgGradientColor.length);
            }
            return this;
        }

        public StyleViewAttr setBgcGradientAngle(float bgcGradientAngle) {
            if(bgcGradientAngle < 0) throw new IllegalArgumentException("角度不能小于0度");
            if(bgcGradientAngle > 90) throw new IllegalArgumentException("角度不能大于90度");
            this.bgcGradientAngle = bgcGradientAngle;
            return this;
        }

        public StyleViewAttr setBgcGradientWeight(float[] bgcGradientWeight) {
            this.bgcGradientWeight = bgcGradientWeight;
            return this;
        }

        public StyleViewAttr setBorderWidth(int borderWidth) {
            this.borderWidth = borderWidth;
            return this;
        }

        public StyleViewAttr setBorderColor(int borderColor) {
            this.borderColor = borderColor;
            return this;
        }

        public StyleViewAttr setShadowColor(int shadowColor) {
            this.shadowColor = shadowColor;
            return this;
        }

        public StyleViewAttr setShadowWidth(int shadowWidth) {
            if(bgcGradientAngle < 0) throw new IllegalArgumentException("阴影宽度不能为负数");
            this.shadowWidth = shadowWidth;
            return this;
        }

        public StyleViewAttr setShadowOffsetX(int shadowOffsetX) {
            this.shadowOffsetX = shadowOffsetX;
            return this;
        }

        public StyleViewAttr setShadowOffsetY(int shadowOffsetY) {
            this.shadowOffsetY = shadowOffsetY;
            return this;
        }
    }
}


在attrs中设置的属性:

<declare-styleable name="StyleLayout">
        <!--对应位置的圆角大小-->
        <attr name="left_top_round" format="dimension"/>
        <attr name="left_bottom_round" format="dimension"/>
        <attr name="right_top_round" format="dimension"/>
        <attr name="right_bottom_round" format="dimension"/>
        <attr name="all_round" format="dimension"/>
        <!--背景渐变色之开头颜色-->
        <attr name="bgGradientStartColor" format="color"/>
        <!--背景渐变色之结尾颜色-->
        <attr name="bgGradientEndColor" format="color"/>
        <!--背景渐变色之开头颜色在整个控件中的比重-->
        <attr name="bgGradientStartWeight" format="float"/>
        <!--背景渐变色之结尾颜色在整个控件中的比重-->
        <attr name="bgGradientEndWeight" format="float"/>
        <!--背景渐变色方向 角度[0°-90°]-->
        <attr name="bgcGradientAngle" format="float"/>
        <!--边框宽度-->
        <attr name="borderWidth" format="dimension"/>
        <!--边框颜色-->
        <attr name="borderColor" format="color"/>
        <!--阴影颜色-->
        <attr name="shadowColor" format="color"/>
        <!--阴影宽度-->
        <attr name="shadowWidth" format="dimension"/>
        <!--阴影水平偏移-->
        <attr name="shadowOffsetX" format="enum">
            <enum name="left_offset" value="-1"/>
            <enum name="vertical_middle" value="0"/>
            <enum name="right_offset" value="1"/>
        </attr>
        <!--阴影垂直偏移-->
        <attr name="shadowOffsetY" format="enum">
            <enum name="top_offset" value="-1"/>
            <enum name="horizontal_middle" value="0"/>
            <enum name="bottom_offset" value="1"/>
        </attr>
    </declare-styleable>
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-04-07 23:02:30  更:2022-04-07 23:03:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/16 20:49:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码