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
相关推荐
Ticnix15 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人18 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl22 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅25 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人33 分钟前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼37 分钟前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空40 分钟前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_1 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus1 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空1 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范