Android自定义View合集

news2024/11/24 18:35:35

文章目录

  • 自定义QQ步数
    • QQ计步效果分析
    • 自定义View分析的常用步骤
    • 自定义属性
    • 获取自定义属性
    • 画外圆弧
    • 画内圆
    • 画文字
    • 增加动画让其动起来
  • 自定义评分控件RatingBar
    • 自定义评分View效果分析
    • 自定义属性
    • 获取自定义属性
    • 重写onMeasure()方法
    • 画出对应数量的星星
    • 触摸事件处理
  • 自定义酷狗侧滑菜单
    • 实现方式
    • 代码实现

自定义QQ步数

QQ计步效果分析

  1. 先画出外面的蓝色的外圆
  2. 画出里面的红色的内圆
  3. 画出中间的文字

自定义View分析的常用步骤

  1. 分析效果
  2. 确定自定义属性,编写attr.xml 文件
  3. 在布局中使用
  4. 在自定义View中获取自定义属性
  5. 开始具体逻辑画View

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="QQStepView">
        <attr name="outColor" format="color" />
        <attr name="innerColor" format="color" />
        <attr name="stepTextColor" format="color" />
        <attr name="stepTextSize" format="dimension" />
        //圆环宽度 外圆减去内圆
        <attr name="borderWidth" format="dimension"/>
        //内圆半径
        <attr name="radius" format="dimension"/>
    </declare-styleable>
</resources>

获取自定义属性

        TypedArray array=context.obtainStyledAttributes(attrs,R.styleable.QQStepView);
        mInnerColor=array.getColor(R.styleable.QQStepView_innerColor,Color.RED);
        mOutColor=array.getColor(R.styleable.QQStepView_outColor,Color.BLUE);
        mBorderWidth= (int)array.getDimension(R.styleable.QQStepView_borderWidth,mBorderWidth)       mStepTextSize=array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize,mStepTextSize);
        mStepTextColor=array.getColor(R.styleable.QQStepView_stepTextColor,mStepTextColor);


        initPaints();
        array.recycle();

画外圆弧

 //startAngle 开始角度 x轴正向是0度 sweepAngle 顺时针扫过的角度
        RectF rectF=new RectF(0,0,getWidth(),getHeight());
        canvas.drawArc(rectF,135,270,false,mOutPaint);

现象:image-20200211102829406

  1. 可以发现最终画出来的圆弧外边少了一部分,少的一部分是mBorderWidth/2,如图中的方形的框就是我们的View的大小,这个大小已经死定了的,我们要向让圆弧全部都显露出来就需要把里面画的区域缩小,这样才能全部显露出来。所以将RectF 缩小 mBorderWidth/2

  2. 画出来的两边是条直线很不美观,需要将其改为圆形边框 pan

     private void initPaints() {
            mOutPaint=new Paint();
            //设置抗锯齿
            mOutPaint.setAntiAlias(true);
            mOutPaint.setStrokeWidth(mBorderWidth);
            mOutPaint.setColor(mOutColor);
            //设置边缘
            mOutPaint.setStrokeCap(Paint.Cap.ROUND);
            //设置空心
            mOutPaint.setStyle(Paint.Style.STROKE);
        }
    

画内圆

内圆的画法跟外圆不一样了,内圆就需要动态的随着步数的变化而变化。定义总共的步数和现在的步数。然后算出比例,根据比例算出应该画的角度。

    //总共的   当前的
    private int mStepMax=100;
    private int currentStep=50;
        //画内圆
        float sweepAngle=(float)currentStep/mStepMax;
        canvas.drawArc(rectF,135,sweepAngle*270,false,mInnerPaint);

画文字

 //画文字
        String setpText=currentStep+"";
        Rect bound=new Rect();
        mTextPaint.getTextBounds(setpText,0,setpText.length(),bound);
        int dx=getWidth()/2-(bound.right-bound.left)/2;
        //基线 baseLine
        Paint.FontMetricsInt fontMetricsInt=mTextPaint.getFontMetricsInt();
        int dy=(fontMetricsInt.bottom-fontMetricsInt.top)/2-fontMetricsInt.bottom;
        int baseLine=getWidth()/2+dy;
        canvas.drawText(setpText,dx,baseLine,mTextPaint);

增加动画让其动起来

    public void setMaxStep(int maxStep){
        this.mStepMax=maxStep;
    }


    public void setCurrentStep(int currentStep){
        this.currentStep=currentStep;
        //重新绘制
        invalidate();
    }
public class MainActivity extends AppCompatActivity {

    ObjectAnimator objectAnimator;
    ValueAnimator valueAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        final QQStepView qqStepView=findViewById(R.id.stepView);
        qqStepView.setMaxStep(4000);
        valueAnimator=ObjectAnimator.ofInt(0,2000);
        valueAnimator.setInterpolator(new AccelerateInterpolator());
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value= (int) animation.getAnimatedValue();
                qqStepView.setCurrentStep(value);

            }
        });
        valueAnimator.start();

    }
}

自定义评分控件RatingBar

自定义评分View效果分析

  1. 两张星星图片
  2. 星星数量

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RatingBar">
        <attr name="starNormal" format="reference"/>
        <attr name="starFocus" format="reference"/>
        <attr name="starNum" format="integer"/>
    </declare-styleable>
</resources>

获取自定义属性

  TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RatingBar);

        int starNormalId = typedArray.getResourceId(R.styleable.RatingBar_starNormal, 0);

        if (starNormalId == 0) {
            throw new RuntimeException("请设置属性 starNormal");
        }
        mStarNormalBitmap = BitmapFactory.decodeResource(getResources(), starNormalId);

        int starFocusId = typedArray.getResourceId(R.styleable.RatingBar_starFocus, 0);

        if (starFocusId == 0) {
            throw new RuntimeException("请设置属性 starNormal");
        }

        mStarFocusBitmap = BitmapFactory.decodeResource(getResources(), starFocusId);

        mStarNum = typedArray.getInt(R.styleable.RatingBar_starNum, mStarNum);

重写onMeasure()方法

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //指定控件宽高 控件的高即是星星的高

        int starHeight = mStarFocusBitmap.getHeight();
        int starWidth = mStarFocusBitmap.getWidth();

        int height = starHeight + getPaddingBottom() + getPaddingTop();
        int width = 0;
        for (int i = 0; i < mStarNum; i++) {
            width = starWidth + width + mSpace;
        }


        setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), height);


    }

画出对应数量的星星

for (int i = 0; i < mStarNum; i++) {
    canvas.drawBitmap(mStarNormalBitmap, mStarFocusBitmap.getWidth() * i + mSpace * i, 0, null);
}

触摸事件处理

 public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
//                int moveX= (int) event.getX();
//                mNeedDraw=getNeedDrawNum(moveX);
//                if(mNeedDraw>0){
//                    invalidate();
//                }
            case MotionEvent.ACTION_MOVE:
                int moveX= (int) event.getX();
                mNeedDraw=getNeedDrawNum(moveX);
                if(mNeedDraw>0){
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
    //这里一定要reture true 如果reture super.onTouchEvent 就是false false 表示不消耗这个事件,就不会继续调用   Move 方法了 
        return true;
    }

自定义酷狗侧滑菜单

实现方式

  1. 继承自定义HorizontalScrollView ,写好两个布局(menu,content) ,运行起来
  2. 运行起来后布局是全部乱套的,menu ,content 宽度不对,需要调整
  3. 默认抽屉式关闭的,手指抬起的时候要判断是打开还是关闭状态
  4. 快速滑动的情况下需要处理
  5. 处理内容部分的缩放,菜单部分有位移和透明度
  6. 充分考虑Touch 事件分发

代码实现

package com.cailei.slidemenu;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;

import androidx.core.view.ViewCompat;

/**
 * @author : cailei
 * @date : 2020-03-23 18:02
 * @description :
 */
public class SlidingMenu extends HorizontalScrollView {

    private int menuWidth;
    private int screenWidth;

    private ViewGroup mCotentView;
    private ViewGroup mMenuView;

    GestureDetector gestureDetector;

    private boolean isMenuOpen;
    private boolean mIsIntecept = false;

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

    public SlidingMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
        int menuRight = array.getInteger(R.styleable.SlidingMenu_SlidingMenu_rightMargin, ScreenUtils.dip2px(context, 50));
        menuWidth = ScreenUtils.getScreenWidth(context) - menuRight;
        array.recycle();

        gestureDetector = new GestureDetector(context, mGestureDetector);
    }

    private GestureDetector.OnGestureListener mGestureDetector = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            Log.e("TAG", "velocityX" + "->" + velocityX);
            //小于0 快速往左滑动 大于0 快速往右滑动
            if (isMenuOpen) {
                //如果侧边的菜单栏打开,快速往左滑动就关闭
                if (velocityX < 0) {
                    closeMenu();
                    return true;
                }
            } else {
                if (velocityX > 0) {
                    openMenu();
                    return true;
                }
            }
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    };

    //宽度不对,需要知道宽高

    @Override
    protected void onFinishInflate() {
        //布局加载完毕会调用这个方法



        super.onFinishInflate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        ViewGroup container = (ViewGroup) getChildAt(0);
        mMenuView = (ViewGroup) container.getChildAt(0);
        mMenuView.getLayoutParams().width = menuWidth;

        mCotentView = (ViewGroup) container.getChildAt(1);
        mCotentView.getLayoutParams().width = ScreenUtils.getScreenWidth(this.getContext());
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        //初始化进来是关闭状态
        closeMenu();
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if(mIsIntecept){
            return true;
        }
        if (gestureDetector.onTouchEvent(ev)) {
            return true;
        }

        //快速滑动触发了就不要执行

        if (ev.getAction() == MotionEvent.ACTION_UP) {
            //手指抬起 根据当前滚动的距离`来判断
            //特别注意getScrollX 是相对与最开始的0坐标点的,不是相对于自己的手指的
            int currentScrollX = getScrollX();
            if (currentScrollX > menuWidth / 2) {
                //超过菜单的一半 关闭
                closeMenu();
            } else {
                openMenu();
            }
            //确保super.onTouchEvent 不会执行
            return true;
        }
        return super.onTouchEvent(ev);
    }


    //处理各种缩放


    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        Log.e("TAG", l + "");
        //算一个梯度值
        float scale = 1f * l / menuWidth; //从1-0
        //右边缩放最小时0.7 最大是1
        float rightScale = 0.7f + 0.3f * scale;
        float leftScale = 1.0f - 0.3f * scale;
        //设置缩放中心点,否则就会缩到看不见
        ViewCompat.setPivotX(mCotentView, 0);
        ViewCompat.setPivotY(mCotentView, mCotentView.getMeasuredHeight() / 2);
        ViewCompat.setScaleX(mCotentView, rightScale);
        ViewCompat.setScaleY(mCotentView, rightScale);

        //菜单的缩放和透明度 回拉的时候慢慢变成半透明 半透明-不透明 0.5f - 1f 缩放到不缩放  0.7f-1.0f
        float alpha = 1f - 0.5f * scale;
        ViewCompat.setAlpha(mMenuView, leftScale);
//        ViewCompat.setTranslationY(mMenuView,leftScale);
        ViewCompat.setScaleX(mMenuView, leftScale);
        ViewCompat.setScaleY(mMenuView, leftScale);


        //最后一个效果 退出这个按钮刚开始是在右边,按照我们目前的方式退出的出字永远都是在左边
        //设置平移
        ViewCompat.setTranslationX(mMenuView, 0.2f * l);


    }

    private void closeMenu() {
        smoothScrollTo(menuWidth, 0);
        isMenuOpen = false;
    }

    private void openMenu() {
        smoothScrollTo(0, 0);
        isMenuOpen = true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        mIsIntecept=false;
        if (isMenuOpen) {
            if (ev.getX() > menuWidth) {
                closeMenu();
                mIsIntecept=true;
                return true;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/685025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu设置无线wifi的静态IP

安装 net-tools sudo apt install net-tools 输入ifconfig查看当前网络ip地址&#xff1a; pulsarpulsar:~$ ifconfig docker0: flags4099<UP,BROADCAST,MULTICAST> mtu 1500inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255ether 02:42:02:8c:87:a…

Linux下使用Mysql数据库忘记密码问题解决

​ 方法1查看初始化密码进行登录&#xff1a; 查看mysql的初始密码 在rootlocalhost后面的就是mysql初始的密码&#xff0c;以上图为例 初始密码则为&#xff1a;ukehBfivW1 方法2直接跳过数据库密码验证&#xff1a; 1、用vi指令进入mysql配置文件/etc/my.cnf并修改 在最后…

jmeter的web接口测试

目录 前言&#xff1a; 一、安装Jmeter 二、添加HTTP接口测试 三、添加新的POST请求 四、添加断言 前言&#xff1a; 使用JMeter进行Web接口测试是一种常见的应用场景。 一、安装Jmeter 二、添加HTTP接口测试 我们的所以工具都会在Jmeter工具中完成&#xff0c;接来就…

python爬虫之Scrapy框架--测试调试--保存数据

目录 ScrapyShell 启动ScrapyShell 基本方法 注意 保存数据到文件 方法一 使用python原生方式保存 方法二 使用Scrapy内置方式 方法三 Item Pipeline的使用 功能 ScrapyShell ScrapyShell是Scrapy框架提供的一个交互式的开发工具&#xff0c;用于调试和测试爬虫&…

数学建模-数据的处理

MATLAB数学建模方法与实践&#xff08;第3版&#xff09;——读书笔记 数据的准备数据获取数据处理缺失值处理噪音过滤数据集成数据归约数据变换标准化离散化 数据统计基本描述性统计分布描述性统计 数据可视化数据降维主成分分析&#xff08;PCA&#xff09;相关系数降维 数据…

K8S 使用(1)- 基本命令

根据上文完成部署k8s后&#xff0c;我们需要了解如何使用k8s.首先我们需要了解如何使用K8S的一些基础命令及主要概念。 尚不清楚如何部署的请参考&#xff1a;单master部署简要步骤 如果您熟悉了k8s基本命令&#xff0c;可以跳过此章节。 目录 先看一下这张图 2. 节点 nod…

2.2、IOC容器的实现流程

​一、类图 二、容器的实现接口 Spring容器并不是只有一个。Spring自带多个容器实现&#xff0c;可以归位两种不同的类型。 &#xff08;1&#xff09;Bean工厂&#xff08;由org.springframework.beans.factory.BeanFactory接口定义&#xff09;是简单的容器实现工厂&#x…

数学内容的概述

前言 作为一名文科生&#xff0c;学习编程最大的阻碍莫过于数学知识。 学习数学的必要性 跟开发APP和后台服务器相比&#xff0c;机器学习、深度学习需要大量的数学知识。 数据处理和清洗&#xff1a;数据分析涉及大量的数据处理和清洗工作&#xff0c;需要掌握基本的数学概…

linux系统LAMP架构

文章目录 一、LAMP简介与概述二、LAMP各组件主要作用1.构建LAMP平台顺序 三、编译安装Apache httpd服务1.将所需软件安装包下载到/opt目录下解压2.移动两个文件并改名3.安装依赖环境4.编译安装5.做软连接&#xff0c;使文件可执行6.优化配置文件路径&#xff0c;并把httpd服务的…

《面试1v1》Redis主从架构

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

crossover虚拟机软件好用吗?22.1.1版本有哪些优点

苹果系统虽然安全稳定&#xff0c;但有些应用程序并不提供苹果系统的安装包&#xff0c;使用双系统或在苹果电脑安装虚拟机可以帮我们提升设备使用效率&#xff0c;除了这两种解决方案外&#xff0c;还可以使用类虚拟机软件crossover&#xff0c;crossover mac的优点在于不会占…

使用HTTP/2实现服务端主动推送消息给客户端

77. 使用HTTP/2实现服务端主动推送消息给客户端 HTTP/2协议的服务器主动推送机制是通过服务器在接收到客户端请求后&#xff0c;主动向客户端推送相关资源的方式来实现的。下面将详细解释如何在服务器端和客户端实现HTTP/2的服务器主动推送&#xff0c;并给出相应的代码示例。…

华为OD机试真题(Java),素数伴侣(100%通过+复盘思路)

一、题目描述 若两个正整数的和为素数&#xff0c;则这两个正整数称之为“素数伴侣”&#xff0c;如2和5、6和13&#xff0c;它们能应用于通信加密。现在密码学会请你设计一个程序&#xff0c;从已有的 N &#xff08; N 为偶数&#xff09;个正整数中挑选出若干对组成“素数伴…

老鸟是这样实现springboot日志打印的~

文章目录 前言一、实现一个全局日志打印二、使用步骤1. 新增一个自定义注解2. 拦截注解,并实现相应的打印日志功能3. 使用 总结 前言 项目中有时候为了与前端,与后端(微服务/远程调用http) 等撕逼,我们不得不做好应对措施,最终的就是打印清晰我们的入参出参日志,这为以后撕逼,…

Nik Silver Efex 黑白胶片效果滤镜

Nik Silver Efex 为获得优质黑白效果而精心设计算法&#xff0c;是世界领先级的黑白胶片滤镜集。 内置 64 个黑白&#xff08;包括单色、双色等&#xff09;预设供选择&#xff0c;主要分为经典 Classic、现代 Modern、复古 Vintage、阿弗格 En Vogue等四大风格&#xff0c;另外…

银行数字化转型导师坚鹏:兴业银行《天才与算法》读书拆解培训

兴业银行杭州分行《天才与算法》读书拆解培训圆满结束 兴业银行股份有限公司&#xff08;简称“兴业银行”&#xff09;成立于1988年8月&#xff0c;2022年总资产9.27万亿元&#xff0c;是经国务院、中国人民银行批准成立的首批股份制商业银行之一&#xff0c;总行设在福州市。…

【MySQL 高级(进阶)SQL 语句】

目录 一、命令操作1、select ----显示表格中一个或数个字段的所有数据记录2、select 指定字段的显示顺序3、select distinct 不显示重复的数据记录4、where 有条件的查询5、and和or 命令 ---- 且和或6、in 显示已知的值的数据记录7、between 显示两个值范围内的数据记录8、通配…

最短路径算法(Python数学建模)

0. 前言 最短路径算法是一种用于计算图中两个节点之间最短路径的算法。在图论中&#xff0c;最短路径通常指的是图中连接两个节点的路径中具有最小权重&#xff08;或成本&#xff09;的路径。 以下是两种常见的最短路径算法&#xff1a; Dijkstra算法&#xff1a;Dijkstra算…

Python对csv文件一键多值保存为json本地文件再读取加速效率(3)

最近发现做办公自动化表格匹配的时候还是csv格式的文件最快、效率是最高的 今天接到一个需求就是大致内容之这样的 1、给我一张表格直邮一列A列&#xff0c;内容是运单号 2、需要用相同的单号去另外一张表格匹配数据 3、其实就是Excel中的常见的vlookup 但是想要匹配的表格有几…

D351周赛复盘:美丽下标对数目(互质/数学运算)+数组划分若干子数组

文章目录 6466.美丽下标对数目思路互质的含义 python写法cpp写法 6910. 将数组划分成若干好子数组的方式思路完整版ans (ans * (ls[i 1] - ls[i]))含义重要问题1&#xff1a;为什么ls[i 1] - ls[i]能代表所有这两个1划分出来的子数组&#xff1f;重要问题2&#xff1a;为什么…