# Android中如何自定义View ## 前言 在Android应用开发中,系统提供的标准UI控件往往无法满足复杂的业务需求,这时就需要通过自定义View来实现特定的视觉效果和交互逻辑。自定义View是Android开发者必须掌握的核心技能之一,本文将全面讲解自定义View的实现原理、技术要点和最佳实践。 ## 一、自定义View基础概念 ### 1.1 什么是自定义View 自定义View是指继承自Android View类或其子类(如TextView、ImageView等),通过重写相关方法来实现特定绘制逻辑和交互行为的视图组件。根据实现方式不同,可分为: 1. **组合控件**:将多个系统控件组合成新组件 2. **继承系统控件**:扩展现有控件的功能 3. **完全自定义**:继承View类从头实现 ### 1.2 自定义View的核心方法 | 方法名 | 调用时机 | 典型用途 | |--------|----------|----------| | onMeasure() | 确定View大小 | 测量View的宽高 | | onLayout() | 确定子View位置 | 对包含子View的容器有效 | | onDraw() | 绘制View内容 | 执行Canvas绘制操作 | | onTouchEvent() | 处理触摸事件 | 实现交互逻辑 | ## 二、自定义View的实现步骤 ### 2.1 继承View类 ```java public class CircleView extends View { private Paint mPaint; public CircleView(Context context) { this(context, null); } public CircleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.RED); } }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int size = Math.min(width, height); // 保持宽高一致 setMeasuredDimension(size, size); }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int center = getWidth() / 2; int radius = center - 10; canvas.drawCircle(center, center, radius, mPaint); }
在res/values/attrs.xml中添加:
<resources> <declare-styleable name="CircleView"> <attr name="circle_color" format="color"/> <attr name="circle_radius" format="dimension"/> </declare-styleable> </resources>
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.CircleView, defStyleAttr, 0); mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED); mRadius = a.getDimensionPixelSize( R.styleable.CircleView_circle_radius, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics())); a.recycle(); init(); }
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 手指按下 return true; case MotionEvent.ACTION_MOVE: // 手指移动 return true; case MotionEvent.ACTION_UP: // 手指抬起 return true; } return super.onTouchEvent(event); }
private void startAnimation() { ValueAnimator animator = ValueAnimator.ofFloat(0, 360); animator.setDuration(1000); animator.addUpdateListener(animation -> { mRotateDegree = (float) animation.getAnimatedValue(); invalidate(); // 触发重绘 }); animator.start(); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 测量所有子View measureChildren(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; int lineWidth = 0; int lineHeight = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); if (lineWidth + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin > width) { width = Math.max(lineWidth, width); height += lineHeight; lineWidth = 0; lineHeight = 0; } lineWidth += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; lineHeight = Math.max(lineHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); } setMeasuredDimension( resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec)); }
// 使用postInvalidate()在非UI线程更新视图 new Thread(() -> { // 后台计算 postInvalidate(); }).start();
public class DownloadButton extends View { // 省略部分代码... @Override protected void onDraw(Canvas canvas) { // 绘制背景圆 canvas.drawCircle(mCenterX, mCenterY, mRadius, mBgPaint); // 绘制进度弧 RectF rectF = new RectF( mCenterX - mRadius, mCenterY - mRadius, mCenterX + mRadius, mCenterY + mRadius); canvas.drawArc(rectF, -90, mProgress * 3.6f, false, mProgressPaint); // 绘制进度文本 String text = mProgress + "%"; canvas.drawText(text, mCenterX - mTextPaint.measureText(text) / 2, mCenterY - (mTextPaint.descent() + mTextPaint.ascent()) / 2, mTextPaint); } public void setProgress(int progress) { mProgress = progress; invalidate(); } }
自定义View是Android开发中极具挑战性又充满创造力的工作。通过本文的系统学习,你应该已经掌握了:
建议读者通过实际项目练习来巩固这些知识,逐步掌握更复杂的效果实现。记住优秀的自定义View应该具备:功能完善、性能高效、扩展性强三大特点。
”`
(注:实际字数约4500字,此处为精简版核心内容展示,完整文章包含更多细节说明和代码注释)
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。