9. Android 精通Android高级UI总结:自定义View与动画开发终极实战指南

之前做的案例,包含view,viewGroup,动画,还有他们的综合应用

自定义view,滑动效果

1. 从0到1:Android自定义View打造沉浸式FM收音机刻度滑动控件本文将带你实现一个高度定制化的收音机刻度滑动 - 掘金

2. Android 自定义view 高级UI实战:打造丝滑3D循环滚轮时间选择器这个视图实现了一个可以循环滚动的选择器 - 掘金

自定义viewGroup

3. Android 流式布局打造热门标签 让RecyclerView颤抖!自研流式布局的架构设计与性能突围 - 掘金

4. Android 用户狂赞的UI特效!揭秘折叠卡片+流光动画的终极实现方案高仿:来自鸿蒙智行,华为问界的智仓系统,语 - 掘金

动画:混合动画

5. Android UI动效新标杆:灵动岛动画,动效深度体验!最近换了个新手机,看了下灵动岛动画还是挺炫丽的! 网上 - 掘金

6.android Vivo手机 指纹解锁动画 (附源码)最近换了个新手机,看了下指纹解锁动画还是挺炫丽的! 网上找了 - 掘金

自定义view和动画的综合应用:

7. Android AI大模型 文本打字机效果 流畅光标追踪与智能滚动大模型最近很火,deepSeek, 豆包 - 掘金

8.Android Vivo X200S手机充电动画 PK 遥遥领先Huawei手机充电动画最近换了个新手机VIVO - 掘金

高级UI主要涉及9个方面,如下

1.如何自定义view

2.如何自定义ViewGroup

3.如何做动画效果

4.如何写测量onMeasure

5.如何写绘制ondraw

6.如何写绘制onLayout

7.如何实现滑动,滚动

8.如何实现惯性滑动

9.常用的计算

1. 如何自定义 View

步骤:

  1. 继承 View 类 :创建类继承 android.view.View
  2. 重写构造方法 :处理 XML 属性(通过 obtainStyledAttributes)。
  3. 实现 onMeasure() :计算 View 的尺寸(处理 wrap_content)。
  4. 实现 onDraw() :使用 CanvasPaint 绘制内容。
  5. 处理触摸事件 :重写 onTouchEvent()(可选)。

示例代码:

scss 复制代码
public class CircleView extends View {
    private Paint paint;

    public CircleView(Context context) {
        super(context);
        init();
    }

    private void init() {
        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int size = 200; // 默认尺寸
        setMeasuredDimension(
            resolveSize(size, widthMeasureSpec),
            resolveSize(size, heightMeasureSpec)
        );
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int center = getWidth() / 2;
        canvas.drawCircle(center, center, center, paint);
    }
}

2. 如何自定义 ViewGroup

步骤:

  1. 继承 ViewGroup 类 (如 LinearLayout)。
  2. 重写 onMeasure() :遍历子 View 并测量(measureChild)。
  3. 重写 onLayout() :调用子 View 的 layout() 确定位置。
  4. 处理子 View 的 Margin :使用 MarginLayoutParams

示例代码:

java 复制代码
public class CustomLayout extends ViewGroup {
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec); // 测量所有子View
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View child = getChildAt(0);
        child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
    }
}

3. 如何做动画效果

方法:

  • 属性动画(推荐):

    ini 复制代码
    ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f);
    anim.setDuration(1000);
    anim.start();
  • 补间动画(视觉位移,不影响实际位置):

    ini 复制代码
    Animation anim = new TranslateAnimation(0, 100, 0, 0);
    anim.setDuration(1000);
    view.startAnimation(anim);
  • onDraw() 中实现动画 :结合 ValueAnimator 更新变量并调用 invalidate()


4. 如何写测量 onMeasure

关键点:

  • 解析 MeasureSpec(模式:EXACTLY, AT_MOST, UNSPECIFIED)。
  • 处理 wrap_content 的默认尺寸。
  • 调用 setMeasuredDimension() 保存结果。

示例:

ini 复制代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
    int desiredWidth = 100; // wrap_content 时的默认宽度
    
    int finalWidth;
    if (widthMode == MeasureSpec.EXACTLY) {
        finalWidth = widthSize; // 固定值或 match_parent
    } else if (widthMode == MeasureSpec.AT_MOST) {
        finalWidth = Math.min(desiredWidth, widthSize); // wrap_content
    } else {
        finalWidth = desiredWidth;
    }
    
    // 类似方式计算高度
    setMeasuredDimension(finalWidth, finalHeight);
}

5. 如何写绘制 onDraw

步骤:

  1. 使用 Canvas 绘制图形(圆、矩形、路径等)。
  2. 通过 Paint 控制样式(颜色、描边、抗锯齿)。
  3. 考虑 Padding(避免内容紧贴边缘)。

示例:

scss 复制代码
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint();
    paint.setColor(Color.BLUE);
    paint.setAntiAlias(true);
    
    // 考虑 padding
    int left = getPaddingLeft();
    int top = getPaddingTop();
    int right = getWidth() - getPaddingRight();
    int bottom = getHeight() - getPaddingBottom();
    
    canvas.drawRect(left, top, right, bottom, paint);
}

6. 如何写布局 onLayout(ViewGroup)

作用: 确定子 View 的位置和大小。

示例:

ini 复制代码
@Override
protected void onLayout(boolean changed, int parentLeft, int parentTop, int parentRight, int parentBottom) {
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        // 计算子 View 的位置(例如垂直排列)
        int left = 0;
        int top = i * 100;
        int right = left + child.getMeasuredWidth();
        int bottom = top + child.getMeasuredHeight();
        child.layout(left, top, right, bottom);
    }
}

7. 如何实现滑动/滚动

方法:

  1. 使用 scrollTo/scrollBy(内容滑动,非 View 本身移动):

    scss 复制代码
    // 向右滑动 50px(内容向左移动)
    scrollBy(50, 0);
  2. 结合 Scroller 平滑滑动

    scss 复制代码
    Scroller scroller = new Scroller(context);
    
    // 开始滑动
    scroller.startScroll(getScrollX(), 0, 100, 0, 500);
    invalidate();
    
    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }

8. 如何实现惯性滑动

步骤:

  1. 使用 VelocityTracker 获取滑动速度

  2. Scroller.fling() 模拟惯性

    scss 复制代码
    VelocityTracker velocityTracker = VelocityTracker.obtain();
    // 在 onTouchEvent() 中追踪速度
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        velocityTracker.addMovement(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                velocityTracker.computeCurrentVelocity(1000); // 单位:像素/秒
                int velocityX = (int) velocityTracker.getXVelocity();
                scroller.fling(
                    getScrollX(), getScrollY(), 
                    -velocityX, 0,  // 注意方向取反
                    minX, maxX, minY, maxY
                );
                invalidate();
                velocityTracker.recycle();
                break;
        }
        return true;
    }
  3. computeScroll() 中更新位置(同步骤7)。

9.常用计算与图形算法

(1) 贝塞尔曲线 (Bezier Curve)

应用场景:平滑路径绘制、弹性动画、手势轨迹

scss 复制代码
// 二次贝塞尔曲线
Path path = new Path();
path.moveTo(startX, startY);
path.quadTo(controlX, controlY, endX, endY);
canvas.drawPath(path, paint);

// 三次贝塞尔曲线
path.cubicTo(controlX1, controlY1, controlX2, controlY2, endX, endY);
(2) 三角函数

应用场景:圆形布局、旋转动画、角度计算

ini 复制代码
// 计算圆周上点的位置
float centerX = getWidth()/2f;
float centerY = getHeight()/2f;
float radius = 100f;
float angle = (float) (Math.PI * 0.75); // 135度

float x = centerX + radius * (float) Math.cos(angle);
float y = centerY + radius * (float) Math.sin(angle);
(3) 向量运算

应用场景:手势方向判断、物理动画

ini 复制代码
// 计算两点间向量
float dx = point2.x - point1.x;
float dy = point2.y - point1.y;

// 向量长度(距离)
float distance = (float) Math.sqrt(dx * dx + dy * dy);

// 向量标准化
if (distance > 0) {
    float unitX = dx / distance;
    float unitY = dy / distance;
}
(4) 颜色计算

应用场景:渐变动画、状态切换

ini 复制代码
// 颜色插值(线性渐变)
float fraction = currentProgress / maxProgress;
int color = (int) (startColor + fraction * (endColor - startColor));

// HSV颜色空间转换(色相旋转)
float[] hsv = new float[3];
Color.colorToHSV(baseColor, hsv);
hsv[0] = (hsv[0] + 30) % 360; // 色相偏移30度
int newColor = Color.HSVToColor(hsv);
(5) 几何计算

应用场景:碰撞检测、区域判断

ini 复制代码
// 点是否在圆内
boolean inCircle = (pointX - centerX) * (pointX - centerX) 
                 + (pointY - centerY) * (pointY - centerY) 
                 <= radius * radius;

// 点是否在矩形内
Rect rect = new Rect(left, top, right, bottom);
boolean inRect = rect.contains(pointX, pointY);
(6) 插值器 (Interpolator)

应用场景:动画非线性变化

java 复制代码
// 自定义插值器
public class BounceInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float input) {
        return (float) (Math.sin(input * Math.PI * 2) * 0.5 + 0.5);
    }
}

// 使用
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0, 100);
anim.setInterpolator(new BounceInterpolator());
(7) 路径测量 (PathMeasure)

应用场景:沿路径运动、路径长度计算

ini 复制代码
Path path = new Path();
path.addCircle(centerX, centerY, radius, Path.Direction.CW);

PathMeasure pm = new PathMeasure(path, false);
float pathLength = pm.getLength();

// 获取路径上的点
float[] pos = new float[2];
float distance = pathLength * fraction; // 路径百分比
pm.getPosTan(distance, pos, null);
(8) 矩阵变换 (Matrix)

应用场景:图像变换、多点触控

scss 复制代码
Matrix matrix = new Matrix();

// 组合变换
matrix.postTranslate(dx, dy);
matrix.postRotate(degrees, pivotX, pivotY);
matrix.postScale(scaleX, scaleY, pivotX, pivotY);

// 应用到Canvas
canvas.save();
canvas.concat(matrix);
// 绘制内容...
canvas.restore();

10 总结:

  1. 所有自定义View必须实现onMeasureonDraw
  2. ViewGroup额外需要onLayout处理子视图
  3. 动画效果通过修改属性驱动onDraw
  4. 滑动效果依赖Scroller+computeScroll机制
  5. 高级效果基于数学工具库的计算结果
  6. 惯性滑动=触摸事件+速度追踪+Scroller
相关推荐
用户47949283569154 分钟前
面试官:为什么很多格式化工具都会在行尾额外空出一行
前端
知识分享小能手5 分钟前
Vue3 学习教程,从入门到精通,Vue3 中使用 Axios 进行 Ajax 请求的语法知识点与案例代码(23)
前端·javascript·vue.js·学习·ajax·vue·vue3
一大树5 分钟前
首屏白屏的处理方案~嗖得一下
前端
小old弟7 分钟前
🤔同时发送100个请求?!手撕,并发请求⌨️
前端
533_11 分钟前
[echarts] 更新数据
前端·javascript·echarts
excel12 分钟前
理解 JavaScript 中的迭代器协议与中断行为:for...of vs for...in
前端
幻雨様14 分钟前
UE5多人MOBA+GAS 番外篇:同时造成多种类型伤害,以各种属性值的百分比来应用伤害(版本二)
java·前端·ue5
讨厌吃蛋黄酥18 分钟前
利用Mock实现前后端联调的解决方案
前端·javascript·后端
zzywxc78740 分钟前
在处理大数据列表渲染时,React 虚拟列表是提升性能的关键技术,但在实际实现中常遇到渲染抖动和滚动定位偏移等问题。
前端·javascript·人工智能·深度学习·react.js·重构·ecmascript
Hello.Reader1 小时前
Rust → WebAssembly 的性能剖析全指南
前端·rust·wasm