美文网首页
UI 03: 自定义QQ计步器效果

UI 03: 自定义QQ计步器效果

作者: xqiiitan | 来源:发表于2024-06-04 11:08 被阅读0次

1. 自定义属性

    <declare-styleable name="QQStepView">
        <attr name="outerColor" format="color" />
        <attr name="innerColor" format="color" />
        <attr name="borderWidth" format="dimension" />
        <attr name="stepTextSize" format="dimension" />
        <attr name="stepTextColor" format="color" />
    </declare-styleable>

2. 使用自定义组件,设置自定义属性

    <com.tom.architect02.ui10.day03.QQStepView
        android:id="@+id/step_view"
        app:outerColor="@color/purple_200"
        app:innerColor="@color/teal_200"
        app:borderWidth="20dp"
        app:stepTextColor="@color/purple_500"
        app:stepTextSize="30sp"
        android:background="@color/teal_700"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

3. 实现QQStepView

package com.tom.architect02.ui10.day03;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.tom.architect02.R;

// 仿QQ运动计步
public class QQStepView extends View {
    private int mOuterColor = Color.RED;
    private int mInnerColor = Color.BLUE;
    private int mBorderWidth = 20;// 20px
    private int mStepTextSize;
    private int mStepTextColor;

    private Paint mOutPaint, mInnerPaint, mTextPaint;
    // 总共的步数,当前的步数
    private int mStepMax = 0;
    private int mCurrentStep = 0;

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

    public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 1.分析效果;
        // 2.确定自定义属性,编写attrs.xml
        // 3.在布局中使用
        // 4.在自定义View中获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepView);
        mOuterColor = array.getColor(R.styleable.QQStepView_outerColor, mOuterColor);
        mInnerColor = array.getColor(R.styleable.QQStepView_innerColor, mInnerColor);
        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);
        array.recycle();

        mOutPaint = new Paint(); // 初始化外圆的画笔
        mOutPaint.setAntiAlias(true);
        mOutPaint.setStrokeWidth(mBorderWidth);
        mOutPaint.setColor(mOuterColor);
        mOutPaint.setStrokeCap(Paint.Cap.ROUND);
        mOutPaint.setStyle(Paint.Style.STROKE);// 画笔空心

        mInnerPaint = new Paint();
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setStrokeWidth(mBorderWidth);
        mInnerPaint.setColor(mInnerColor);
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
        mInnerPaint.setStyle(Paint.Style.STROKE);// 画笔空心

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(mStepTextColor);
        mTextPaint.setTextSize(mStepTextSize);
        // 5.onMeasure()
        // 6.画外圆弧 ,内圆弧 ,文字
        // 7.其他
    }

    // 5.onMeasure()
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 调用者在布局文件中可能  wrap_content
        // 获取模式 AT_MOST  40dp
        // 宽度高度不一致 取最小值,确保是个正方形
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int size = width > height ? height : width;
        setMeasuredDimension(size, size);
    }

    // 6.画外圆弧 ,内圆弧 ,文字
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 6.1 画外圆弧,确定矩形的区域【坐标系】. 分析:圆弧闭合了  思考:边缘没显示完整.  描边有宽度:mBorderWidth 圆弧
        // int center = getWidth()/2; // 中心点
        // int radius = getWidth()/2 - mBorderWidth/2; // 半径值
        // RectF rectF = new RectF(center-radius,center-radius, center+radius,center+radius);
        RectF rectF = new RectF(mBorderWidth / 2, mBorderWidth / 2
                , getWidth() - mBorderWidth / 2, getHeight() - mBorderWidth / 2);
        // 研究研究,圆弧开始角度,扫过的角度。
        canvas.drawArc(rectF, 135, 270, false, mOutPaint);

        if (mStepMax == 0) return;
        // 6.2 画内圆弧  怎么画肯定不能写死  百分比  是使用者设置的从外面传
        float sweepAngle = (float) mCurrentStep / mStepMax;
        canvas.drawArc(rectF, 135, sweepAngle * 270, false, mInnerPaint);

        // 6.3 画文字
        String stepText = mCurrentStep + "";
        Rect textBounds = new Rect(); // 给bounds赋值。
        mTextPaint.getTextBounds(stepText, 0, stepText.length(), textBounds);
        int dx = getWidth() / 2 - textBounds.width() / 2; // 文字的起始x坐标= 控件的一半 - 文字的一半
        // 确定基线 baseLineY
        Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
        int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
        int baseLineY = getHeight() / 2 + dy;
        canvas.drawText(stepText, dx, baseLineY, mTextPaint);
    }

    // 7.其他: 写几个方法动起来 synchronized 保证多线程操作不出问题。
    public synchronized void setStepMax(int stepMax) {
        this.mStepMax = stepMax;
    }

    public synchronized void setCurrentStep(int currentStep) {
        this.mCurrentStep = currentStep;
        invalidate(); // 不断重新绘制:onDraw()
    }
}

4. invalidate()源码分析-- 最终调用了onDraw()方法。

invalidate()
--> View.invalidate()
--> invalidate(true)
--> invalidateInternal()
--> skipInvalidate()
--> p.invalidateChild(this, damage);
--> ViewGroup::invalidateChild()
--> parent = parent.invalidateChildInParent(location, dirty);
do-while(parent != null) 不断循环到最外层ViewGroup的 invalidateChildInParent()
最外层由于没有parent,调用
ViewRootImpl::invalidateChildInParent()
ViewRootImpl::checkThread()
--> invalidateRectOnScreen()
--> scheduleTraversals()
--> mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
--> TraversalRunnable:: doTraversal()
--> performTraversals() // UI绘制中最重要的方法:performMeasure() performLayout() performDraw()
--> performDraw()
--> draw()
--> drawSoftware()
--> mView.draw(canvas); // mView是最外面的容器view。递归调用。

invalidate 流程:一路往上跑,跑到最外层父容器 调用draw()方法,--> dispatchDraw() 一路往下画,
最终调用invalidate的view 的onDraw() 方法。
invalidate 刷新,牵连着整个layout布局中的view。

 // ViewGroup.java
 parent = parent.invalidateChildInParent(location, dirty);
 if (view != null) {
    // Account for transform on current parent4.                    
    Matrix m = view.getMatrix();                   
    if (!m.isIdentity()) {                        
        RectF boundingRect = attachInfo.mTmpTransformRect;                        
        boundingRect.set(dirty);                        
        m.mapRect(boundingRect);                        
        dirty.set((int) Math.floor(boundingRect.left),                                
        (int) Math.floor(boundingRect.top),                                
        (int) Math.ceil(boundingRect.right),                                
        (int) Math.ceil(boundingRect.bottom));                    
       }                
     }            
   } while (parent != null);
}

为什么不能再子线程中更新UI?

开子线程获取数据,更新UI,一般会调用setText,setImageView,会调用 ViewRootImpl::checkThread() 用来检测线程。
1.if (mThread != Thread.currentThread()) {
Thread.currentThread()是子线程【当前线程】。
mThread 在构造函数中初始化的 Thread.currentThread() 主线程mainThread. ViewRootImpl 在哪里初始化的?
只要2个线程不相等,都会抛异常。

void checkThread() {
    if (mThread != Thread.currentThread()) {            
        throw new CalledFromWrongThreadException(                    
        "Only the original thread that created a view hierarchy can touch its views.");
    }
}

5.如何防止过度渲染

自己写一些界面会非常复杂 QQ空间/WX朋友圈 列表嵌套列表(Item里面布局可能嵌套布局)
1.网上的解决方案
尽量不要嵌套
能不设置背景不要设置背景
........
2.最好的解决方案(蛋疼)
获取到数据去设置 setText()/setImageView 其实, 最终会调用onInvalidate()
最好是自己画,不要用系统的嵌套布局. 运行效率高,但是实现功能效率低(抉择问题)

相关文章

  • iOS 核心动画 第二集

    01-转盘 02-图片折叠 03-音乐震动条 04-倒影 06-QQ粘性效果 实现思路: 1.自定义大圆控件(UI...

  • Android自定义view-QQ计步器效果

    一、概述 二、思路分析 看图说话,图中有两个圆弧,一个背景圆弧,一个能动态变化的圆弧,中间有记录步数的文字。三个对...

  • Android开源库

    UI 之自定义 Behavior 实现 AppBarLayout 越界弹性效果 使用自定义 Behavior 实现...

  • MVP

    UI 之自定义 Behavior 实现 AppBarLayout 越界弹性效果 使用自定义 Behavior 实现...

  • 计步器 APP UI 绘制

    之间我绘制了一个计步器 APP的 UI, 但效果不是很理想,特别是字体部分......这次,我再次用「Sketch...

  • 自定义View学习链接

    01.自定义View简介 - onMeasure,onDraw,自定义属性 03.自定义View - 仿QQ运动步...

  • 自定义View(1)--QQ运动计步器

    无图无真像: 1.根据我们的需要,是需要new出来还是在布局文件里面添加,从而得到相应的构造方法。2.我们需要什么...

  • 计步器UI实现

    UI说明: 1.浅色的表示总共是100%2.深蓝色的表示执行了75%3.数字描述4.单位描述 技术知识: 在Can...

  • Android自定义控件:打造自己的QQ空间主页

    前面已经实现过仿QQ的List抽屉效果以及仿QQ未读消息拖拽效果,具体请见:Android自定义控件:类QQ抽屉效...

  • 自定义View(2) -- 58同城加载动画

    先上图 思考步骤还是和上一篇讲的一样,相同的套路:自定义View(1)--QQ运动计步器 构造器不说了,正常情况一...

网友评论

      本文标题:UI 03: 自定义QQ计步器效果

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