Android官方开发文档Training系列课程中文版:创建自定义View之View的绘制

简介: 原文地址:http://android.xsoftlab.net/training/custom-views/custom-drawing.html#draw自定义View最重要的部分就是它的样子了。

原文地址:http://android.xsoftlab.net/training/custom-views/custom-drawing.html#draw

自定义View最重要的部分就是它的样子了。自定义View的绘制根据应用的需要或者简单亦或者复杂。这节课的内容涵盖了大多数通用的知识点。

重写onDraw()方法

绘制自定义View很重要的一个步骤就是重写它的onDraw()方法。该方法含有一个Canvas对象作为参数,用来使View绘制它本身的内容。Canvas类定义了用于绘制文本,线条,位图以及许多其它基础的物理图形的方法。你可以在onDraw()方法中使用这些方法来创建属于你自己的UI效果。

在开始任何绘制之前,必须先创建一个Paint对象。后面的部分会讨论Paint的相关知识。

创建绘制对象

android.graphics框架将绘制分为了两块区域:

  • 要绘制什么,由Canvas控制
  • 如何绘制,由Paint控制

举个例子,Canvas提供了用于绘制线条的方法,而Paint则定义了线条的颜色。Canvas拥有绘制矩形的方法,而Paint则定义了是否要使颜色填充这个矩形。简而言之,Canvas定义了你绘制在屏幕上的形状,而Paint则定义了这些形状的颜色、风格以及字体等等。

所以,在开始绘制任何事物之前,你需要先创建一个或多个Paint对象。示例PieChart将这些工作放在了一个名为init的方法中,它由构造方法调用:

private void init() {
   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mTextPaint.setColor(mTextColor);
   if (mTextHeight == 0) {
       mTextHeight = mTextPaint.getTextSize();
   } else {
       mTextPaint.setTextSize(mTextHeight);
   }
   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mPiePaint.setStyle(Paint.Style.FILL);
   mPiePaint.setTextSize(mTextHeight);
   mShadowPaint = new Paint(0);
   mShadowPaint.setColor(0xff101010);
   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));
   ...

提前创建对象是一项非常重要的优化手段。View会很频繁的绘制,创建绘制对象的开销十分高昂。在onDraw()方法中创建绘制对象会明显的降低应用的性能,并可能会导致UI出现停顿。

处理布局事件

为了可以正确的绘制View,首先需要知道View的尺寸。复杂一点的View经常需要执行多次布局计算。你永远不要假定View的尺寸,就算是只有一款应用使用了你的View。因为APP需要处理不同的屏幕尺寸,不同的屏幕密度,以及在垂直模式及水平模式下不同的高宽比。

尽管View拥有很多测量尺寸的方法,但是绝大多数是不需要重写的。如果View不需要特别控制它的尺寸,你只需要重写一个方法:onSizeChanged().

onSizeChanged()方法会在首次分配尺寸的时候调用,如果尺寸再次变更时则会再次调用。我们在onSizeChanged()方法中计算View的位置、尺寸以及其它任何与尺寸相关的值,而不是在每次绘制的时候重新计算它们。在示例PieChart中,onSizeChanged()方法内部计算了饼图的矩形边框以及文本和其它可视元素的相对位置。

当View被分配了一个尺寸时,布局管理器会假设该尺寸包含了View所有的内边距。所以在计算View的尺寸时要将View的内边距计算在内。下面的代码段展示了PieChart.onSizeChanged()是如何做的:

       // Account for padding
       float xpad = (float)(getPaddingLeft() + getPaddingRight());
       float ypad = (float)(getPaddingTop() + getPaddingBottom());
       // Account for the label
       if (mShowText) xpad += mTextWidth;
       float ww = (float)w - xpad;
       float hh = (float)h - ypad;
       // Figure out how big we can make the pie.
       float diameter = Math.min(ww, hh);

如果你想更精细的控制View布局的参数,请实现onMeasure()方法。这个方法的两个参数都为View.MeasureSpec。它们用于告诉View的父布局希望View是多大,这个View的尺寸是最大值还是只是一个建议值等等。随着优化,这些值被存放在一个被包装后的整型值内,你必须使用View.MeasureSpec的一个静态方法来提取存储在这个整型值内的信息。

下面是onMeasure()方法的一个示例。在这个实现中,PieCart尝试将自己的区域扩大到内部标签的大小。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   // Try for a width based on our minimum
   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
   // Whatever the width ends up being, ask for a height that would let the pie
   // get as big as it can
   int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
   int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);
   setMeasuredDimension(w, h);
}

这里有三个非常重要的点需要关注:

  • 这个公式将View的内边距一并计算在内。正如前面所说的,这是View本身的职责。
  • 辅助方法resolveSizeAndState()用于创建最终的宽度值与高度值。这个辅助方法通过比较View的期望值与onMeasure()回调的spec值,最后返回一个适当的View.MeasureSpec值。
  • onMeasure()没有返回值。相反的,该方法通过调用setMeasuredDimension()方法与外界交流。调用这个方法是强制要求的。如果你忽略了这个调用,那么View类会抛出一个运行时异常。

绘制

完成对象创建代码与尺寸测量代码之后,接下来就可以实现onDraw()方法了。每个View的onDraw()方法都不相同,但是它们还是有一些相同特点存在的:

  • 使用drawText()方法绘制文本。通过setTypeface()方法指定字体类型,通过setColor()方法设置文本颜色。
  • 通过drawRect()drawOval()drawArc()绘制基础图形。通过setStyle()更改图形是填充模式还是绘制轮廓模式,或者两者都不是。
  • 通过Path类来绘制更加复杂的图形。通过给Path对象添加线条及曲线来定义形状,然后通过drawPath()方法绘制这些形状。只是这些基础形状可以通过setStyle()方法来定义它们的Path风格。
  • 通过创建LinearGradient对象可以定义梯度填充模式。调用setShader()方法来填充形状。
  • 通过drawBitmap()方法绘制位图。

举个例子,下面的代码用来绘制PieChart。它混合使用了文本、线条以及形状。

protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);
   // Draw the shadow
   canvas.drawOval(
           mShadowBounds,
           mShadowPaint
   );
   // Draw the label text
   canvas.drawText(mData.get(mCurrentItem).mLabel, mTextX, mTextY, mTextPaint);
   // Draw the pie slices
   for (int i = 0; i < mData.size(); ++i) {
       Item it = mData.get(i);
       mPiePaint.setShader(it.mShader);
       canvas.drawArc(mBounds,
               360 - it.mEndAngle,
               it.mEndAngle - it.mStartAngle,
               true, mPiePaint);
   }
   // Draw the pointer
   canvas.drawLine(mTextX, mPointerY, mPointerX, mPointerY, mTextPaint);
   canvas.drawCircle(mPointerX, mPointerY, mPointerSize, mTextPaint);
}
目录
相关文章
|
3天前
|
API Android开发 开发者
Android经典实战之使用ViewCompat来处理View兼容性问题
本文介绍Android中的`ViewCompat`工具类,它是AndroidX库核心部分的重要兼容性组件,确保在不同Android版本间处理视图的一致性。文章列举了设置透明度、旋转、缩放、平移等功能,并提供了背景色、动画及用户交互等实用示例。通过`ViewCompat`,开发者可轻松实现跨版本视图操作,增强应用兼容性。
20 5
|
18天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
View的绘制和事件处理是两个重要的主题,上一篇《图解 Android事件分发机制》已经把事件的分发机制讲得比较详细了,这一篇是针对View的绘制,View的绘制如果你有所了解,基本分为measure、layout、draw 过程,其中比较难理解就是measure过程,所以本篇文章大幅笔地分析measure过程,相对讲得比较详细,文章也比较长,如果你对View的绘制还不是很懂,对measure过程掌握得不是很深刻,那么耐心点,看完这篇文章,相信你会有所收获的。
39 2
|
1月前
|
Android开发
Android面试题之自定义View注意事项
在Android开发中,自定义View主要分为四类:直接继承View重写onDraw,继承ViewGroup创建布局,扩展特定View如TextView,以及继承特定ViewGroup。实现时需注意:支持wrap_content通过onMeasure处理,支持padding需在onDraw或onMeasure/onLayout中处理。避免在View中使用Handler,使用post系列方法代替。记得在onDetachedFromWindow时停止线程和动画以防止内存泄漏。处理滑动嵌套时解决滑动冲突,并避免在onDraw中大量创建临时对象。
22 4
|
1月前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
27 1
|
19天前
|
机器学习/深度学习 人工智能 算法
探索AI在医疗影像分析中的应用探索安卓开发中的自定义View组件
【7月更文挑战第31天】随着人工智能技术的飞速发展,其在医疗健康领域的应用日益广泛。本文将聚焦于AI技术在医疗影像分析中的运用,探讨其如何通过深度学习模型提高诊断的准确性和效率。我们将介绍一些关键的深度学习算法,并通过实际代码示例展示这些算法是如何应用于医学影像的处理和分析中。文章旨在为读者提供对AI在医疗领域应用的深刻理解和实用知识。
22 0
|
26天前
|
消息中间件 调度 Android开发
Android经典面试题之View的post方法和Handler的post方法有什么区别?
本文对比了Android开发中`View.post`与`Handler.post`的使用。`View.post`将任务加入视图关联的消息队列,在视图布局后执行,适合视图操作。`Handler.post`更通用,可调度至特定Handler的线程,不仅限于视图任务。选择方法取决于具体需求和上下文。
26 0
|
Android开发
Android Studio 自定义设置注释模板
Android Studio 自定义设置注释模板
439 0
Android Studio 自定义设置注释模板
|
1天前
|
JavaScript 前端开发 Java
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
IT寒冬使APP开发门槛提升,安卓程序员需转型。选项包括:深化Android开发,跟进Google新技术如Kotlin、Jetpack、Flutter及Compose;研究Android底层框架,掌握AOSP;转型Java后端开发,学习Spring Boot等框架;拓展大前端技能,掌握JavaScript、Node.js、Vue.js及特定框架如微信小程序、HarmonyOS;或转向C/C++底层开发,通过音视频项目如FFmpeg积累经验。每条路径都有相应的书籍和技术栈推荐,助你顺利过渡。
9 3
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
|
5天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的世界中,选择正确的平台是关键。本文通过比较安卓和iOS开发的核心差异,揭示平台选择如何影响应用的性能、用户体验和市场覆盖。我们将深入探讨各自的开发环境、编程语言、用户界面设计原则以及发布流程,以帮助开发者和企业做出明智的决策。
24 9
|
2天前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:技术选择的影响
【8月更文挑战第17天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文通过比较这两个平台的编程语言、开发工具及市场策略,揭示了技术选择对开发者和产品成功的重要性。我们将从开发者的视角出发,深入探讨不同平台的技术特性及其对项目实施的具体影响,旨在为即将步入移动开发领域的新手提供一个清晰的指南,同时给予资深开发者新的思考角度。