`

自定义控件(自定义属性、合理设计onMeasure、合理设计onDraw等)(转)

 
阅读更多

转载自:http://blog.csdn.net/carrey1989/article/details/11757409

 

   Android开发做到了一定程度,多少都会用到自定义控件,一方面是更加灵活,另一方面在大数据量的情况下自定义控件的效率比写布局文件更高。

    一个相对完善的自定义控件在布局文件中和java代码中都应能灵活设置属性。另外在普通的布局中和AdapterView中都应能正确绘制,这就要求合理设计onMeasure方法,下文中会做比较详细的讲解。

    本文原创,如需转载,请注明转载地址:http://blog.csdn.net/carrey1989/article/details/11757409

    接下来我就一步一步来讲解如何设计和编写一个比较完善的自定义控件。

    首先要来设计好我们要完成的效果,我们今天来实现下图所示的这样一个控件:

    用文字来描述一下:我们要定义的控件上方会显示一张图片,我们可以设置这张图片的内容,长宽比,透明度,伸缩模式,以及图片四周的填充空间大小。图片下方会显示一行文字,作为一级标题,我们可以设置文字的内容,大小,颜色,以及文字区域四周的填充空间的大小。一级标题下方显示一行二级标题,具体设置内容和一级标题相同。

    我们不妨先来直接看一下完成后的效果,这样可以更直观的了解要实现的控件的样子。

                   

    左图的样子是在常规的布局中自定义控件的样子,右图则是在大数据量的情况下自定义控件作为AdapterView的item的时候绘制出来的样子。

    上面我们大体完成了初步的控件设计,下面我们开始编写代码。

 

    第一步,我们写好自定义属性,根据我们上面所做的设计,我们的自定义属性涉及到三个方面,分别是图片相关的属性,一级标题相关的属性,二级标题相关的属性。

    按照惯例,我们首先在res/values文件目录下创建一个attrs.xml文件。

    然后我们在attrs.xml文件中完成我们对属性的定义,代码片段如下:

 

 

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="imageSrc" format="reference"/>
    <attr name="imageAspectRatio" format="float"/>
    <attr name="imageAlpha" format="float"/>
    <attr name="imagePaddingLeft" format="dimension"/>
    <attr name="imagePaddingTop" format="dimension"/>
    <attr name="imagePaddingRight" format="dimension"/>
    <attr name="imagePaddingBottom" format="dimension"/>
    <attr name="imageScaleType">
        <enum name="fillXY" value="0"/>
        <enum name="center" value="1"/>
    </attr>
    
    <attr name="titleText" format="string"/>
    <attr name="titleTextSize" format="dimension"/>
    <attr name="titleTextColor" format="color"/>
    <attr name="titlePaddingLeft" format="dimension"/>
    <attr name="titlePaddingTop" format="dimension"/>
    <attr name="titlePaddingRight" format="dimension"/>
    <attr name="titlePaddingBottom" format="dimension"/>
    
    <attr name="subTitleText" format="string"/>
    <attr name="subTitleTextSize" format="dimension"/>
    <attr name="subTitleTextColor" format="color"/>
    <attr name="subTitlePaddingLeft" format="dimension"/>
    <attr name="subTitlePaddingTop" format="dimension"/>
    <attr name="subTitlePaddingRight" format="dimension"/>
    <attr name="subTitlePaddingBottom" format="dimension"/>
    
    <declare-styleable name="CustomView">
        <attr name="imageSrc"/>
        <attr name="imageAspectRatio" />
        <attr name="imageAlpha" />
        <attr name="imagePaddingLeft" />
        <attr name="imagePaddingTop" />
        <attr name="imagePaddingRight" />
        <attr name="imagePaddingBottom" />
        <attr name="imageScaleType" />
        <attr name="titleText" />
        <attr name="titleTextSize" />
        <attr name="titleTextColor" />
        <attr name="titlePaddingLeft" />
        <attr name="titlePaddingTop" />
        <attr name="titlePaddingRight" />
        <attr name="titlePaddingBottom" />
        <attr name="subTitleText" />
        <attr name="subTitleTextSize" />
        <attr name="subTitleTextColor" />
        <attr name="subTitlePaddingLeft" />
        <attr name="subTitlePaddingTop" />
        <attr name="subTitlePaddingRight" />
        <attr name="subTitlePaddingBottom" />
    </declare-styleable>
</resources>

 

 

    这里需要说明几点:<attr>标签的format属性值代表属性的类型,这个类型值一共有10种,分别是:reference,float,color,dimension,boolean,string,enum,integer,fraction,flag

 

。但是我们作为开发者常用的基本上只有reference,float,color,dimension,boolean,string,enum这7种。在attrs.xml文件中的<declare-styleable>标签的name属性的值,按照惯例我们都是写成自定义控件类的名字。一个同名的<attr>在attrs.xml中只可以定义一次。

    除此之外,上面的代码都是针对前面的设计来定义了各种属性,相信各位同学都能看懂。

 

    第二步就是编写我们自定义控件的java类了,我们首先将之前做的自定义属性在自定义控件类中做好声明:

 

 

	/** 图片Bitmap */
	private Bitmap imageBitmap;
	/** 图片的长宽比 */
	private float imageAspectRatio;
	/** 图片的透明度 */
	private float imageAlpha;
	/** 图片的左padding*/
	private int imagePaddingLeft;
	/** 图片的上padding */
	private int imagePaddingTop;
	/** 图片的右padding */
	private int imagePaddingRight;
	/** 图片的下padding */
	private int imagePaddingBottom;
	/** 图片伸缩模式 */
	private int imageScaleType;
	/** 图片伸缩模式常量 fillXY */
	private static final int SCALE_TYPE_FILLXY = 0;
	/** 图片伸缩模式常量 center */
	private static final int SCALE_TYPE_CENTER = 1;
	
	/** 标题文本内容 */
	private String titleText;
	/** 标题文本字体大小 */
	private int titleTextSize;
	/** 标题文本字体颜色 */
	private int titleTextColor;
	/** 标题文本区域左padding */
	private int titlePaddingLeft;
	/** 标题文本区域上padding */
	private int titlePaddingTop;
	/** 标题文本区域右padding */
	private int titlePaddingRight;
	/** 标题文本区域下padding */
	private int titlePaddingBottom;
	
	/** 子标题文本内容 */
	private String subTitleText;
	/** 子标题文本字体大小 */
	private int subTitleTextSize;
	/** 子标题文本字体颜色 */
	private int subTitleTextColor;
	/** 子标题文本区域左padding */
	private int subTitlePaddingLeft;
	/** 子标题文本区域上padding */
	private int subTitlePaddingTop;
	/** 子标题文本区域右padding */
	private int subTitlePaddingRight;
	/** 子标题文本区域下padding */
	private int subTitlePaddingBottom;
	
	/** 控件用的paint */
	private Paint paint;
	private TextPaint textPaint;
	/** 用来界定控件中不同部分的绘制区域 */
	private Rect rect;
	/** 宽度和高度的最小值 */
	private static final int MIN_SIZE = 12;
	/** 控件的宽度 */
	private int mViewWidth;
	/** 控件的高度 */
	private int mViewHeight;

 

 

 然后我们要在构造方法中,将从布局文件中读取的自定义属性解析出来。

 

 

		TypedArray a = context.getTheme().obtainStyledAttributes(
				attrs, R.styleable.CustomView, defStyle, 0);
		int n = a.getIndexCount();
		for (int i = 0; i < n; i++) {
			int attr = a.getIndex(i);
			switch (attr) {
			case R.styleable.CustomView_imageSrc:
				imageBitmap = BitmapFactory.decodeResource(
						getResources(), a.getResourceId(attr, 0));
				break;
			case R.styleable.CustomView_imageAspectRatio:
				imageAspectRatio = a.getFloat(attr, 1.0f);//默认长宽相等
				break;
			case R.styleable.CustomView_imageAlpha:
				imageAlpha = a.getFloat(attr, 1.0f);//默认不透明
				if (imageAlpha > 1.0f) imageAlpha = 1.0f;
				if (imageAlpha < 0.0f) imageAlpha = 0.0f;
				break;
			case R.styleable.CustomView_imagePaddingLeft:
				imagePaddingLeft = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_imagePaddingTop:
				imagePaddingTop = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_imagePaddingRight:
				imagePaddingRight = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_imagePaddingBottom:
				imagePaddingBottom = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_imageScaleType:
				imageScaleType = a.getInt(attr, 0);
				break;
			case R.styleable.CustomView_titleText:
				titleText = a.getString(attr);
				break;
			case R.styleable.CustomView_titleTextSize:
				titleTextSize = a.getDimensionPixelSize(
						attr, (int) TypedValue.applyDimension(
								TypedValue.COMPLEX_UNIT_SP, 25, getResources().getDisplayMetrics()));//默认标题字体大小25sp
				break;
			case R.styleable.CustomView_titleTextColor:
				titleTextColor = a.getColor(attr, 0x00000000);//默认黑色字体
				break;
			case R.styleable.CustomView_titlePaddingLeft:
				titlePaddingLeft = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_titlePaddingTop:
				titlePaddingTop = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_titlePaddingRight:
				titlePaddingRight = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_titlePaddingBottom:
				titlePaddingBottom = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_subTitleText:
				subTitleText = a.getString(attr);
				break;
			case R.styleable.CustomView_subTitleTextSize:
				subTitleTextSize = a.getDimensionPixelSize(attr, 
						(int) TypedValue.applyDimension(
								20, TypedValue.COMPLEX_UNIT_SP, getResources().getDisplayMetrics()));//默认子标题字体大小20sp
				break;
			case R.styleable.CustomView_subTitleTextColor:
				subTitleTextColor = a.getColor(attr, 0x00000000);
				break;
			case R.styleable.CustomView_subTitlePaddingLeft:
				subTitlePaddingLeft = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_subTitlePaddingTop:
				subTitlePaddingTop = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_subTitlePaddingRight:
				subTitlePaddingRight = a.getDimensionPixelSize(attr, 0);
				break;
			case R.styleable.CustomView_subTitlePaddingBottom:
				subTitlePaddingBottom = a.getDimensionPixelSize(attr, 0);
				break;
			}
		}
		a.recycle();

 

 

    这里需要说明几点,TypedArray对象在使用完毕后一定要调用recycle()方法。我之前曾在一篇文章中总结过在java代码中进行px与dip(dp)、px与sp单位值的转换实际上,android中也提供了单位转换的函数,我们也可以使用TypedValue.applyDimension(int unit, float value, DisplayMetrics metrics)方法来进行单位的互换,其中,第一个参数是你想要得到的单位,第二个参数是你想得到的单位的数值,比如:我要得到一个25sp,那么我就用TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25,getResources().getDisplayMetrics()),返回的就是25sp对应的px数值了。

 

 

    接下来我们要开始设计onMeasure方法,再设计onMeasure之前我们简单了解几个概念。

    MeasureSpec的三种模式:

    EXACTLY:表示我们设置了MATCH_PARENT或者一个准确的数值,含义是父布局要给子布局一个确切的大小。

    AT_MOST:表示子布局将被限制在一个最大值之内,通常是子布局设置了wrap_content。

    UNSPECIFIED:表示子布局想要多大就可以要多大,通常出现在AdapterView中item的heightMode中。

    了解了上面几个概念,我们就可以开始设计onMeasure了,具体代码如下:

 

 

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		Log.d(TAG , "============onMeasure============");
		Log.d(TAG , "widthMode:" + widthMode);
		Log.d(TAG , "widthSize:" + widthSize);
		Log.d(TAG , "heightMode:" + heightMode);
		Log.d(TAG , "heightSize:" + heightSize);
		Log.d(TAG , "============onMeasure============");
		Log.d(TAG , "============next============");
		
		int width;
		int height;

		if (widthMode == MeasureSpec.EXACTLY) {
			width = widthSize;
		} else {
			int desired = getPaddingLeft() + getPaddingRight()
					+ imagePaddingLeft + imagePaddingRight;
			desired += (imageBitmap != null) ? imageBitmap.getWidth() : 0;
			width = Math.max(MIN_SIZE, desired);
			if (widthMode == MeasureSpec.AT_MOST) {
				width = Math.min(desired, widthSize);
			}
		}

		if (heightMode == MeasureSpec.EXACTLY) {
			height = heightSize;
		} else {
			int rawWidth = width - getPaddingLeft() - getPaddingRight();
			int desired = (int) (getPaddingTop() + getPaddingBottom() + imageAspectRatio
					* rawWidth);

			if (titleText != null) {
				paint.setTextSize(titleTextSize);
				FontMetrics fm = paint.getFontMetrics();
				int textHeight = (int) Math.ceil(fm.descent - fm.ascent);
				desired += (textHeight + titlePaddingTop + titlePaddingBottom);
			}

			if (subTitleText != null) {
				paint.setTextSize(subTitleTextSize);
				FontMetrics fm = paint.getFontMetrics();
				int textHeight = (int) Math.ceil(fm.descent - fm.ascent);//http://hz-chenwenbiao-91.iteye.com/blog/2082075
				desired += (textHeight + subTitlePaddingTop + subTitlePaddingBottom);
			}

			height = Math.max(MIN_SIZE, desired);
			if (heightMode == MeasureSpec.AT_MOST) {
				height = Math.min(desired, heightSize);
			}
		}

		/**
		 * 见http://www.cnblogs.com/mengdd/p/3332882.html
		 * 
		 * 测量过程(measuring pass)是在measure(int, int)中实现的,是从树的顶端由上到下进行的。
		  在这个递归过程中,每一个View会把自己的dimension specifications传递下去。
		  在measure pass的最后,每一个View都存储好了自己的measurements,即测量结果。
		 */
		setMeasuredDimension(width, height);
	}

 

 

 

    思路是这样的:我们首先判断是不是EXACTLY模式,如果是,那就可以直接设置值了,如果不是,我们先按照UNSPECIFIED模式处理,让子布局得到自己想要的最大值,然后判断是否是AT_MOST模式,来做最后的限制。

 

 

    完成onMeasure过程之后,我们需要开始onDraw的设计,在onDraw中我们需要考虑各个部分设置的padding值,然后对应做出坐标的处理,整体的思路是从下向上绘制。具体的代码如下:

 

 

@Override
	protected void onDraw(Canvas canvas) {
		rect.left = getPaddingLeft();
		rect.top = getPaddingTop();
		rect.right = mViewWidth - getPaddingRight();
		rect.bottom = mViewHeight - getPaddingBottom();

		paint.setAlpha(255);
		if (subTitleText != null) {
			paint.setTextSize(subTitleTextSize);
			paint.setColor(subTitleTextColor);
			paint.setTextAlign(Paint.Align.LEFT);

			FontMetrics fm = paint.getFontMetrics();
			int textHeight = (int) Math.ceil(fm.descent - fm.ascent);

			/**
			 * getPaddingLeft()是这个自定义控件的paddingLeft
			 */
			int left = getPaddingLeft() + subTitlePaddingLeft;
			int right = mViewWidth - getPaddingRight() - subTitlePaddingRight;
			int bottom = mViewHeight - getPaddingBottom()
					- subTitlePaddingBottom;

			/**
			 * 应该是画text串为availableTextWidth宽,如果超过宽度,就使用...来结束它
			 */
			int availableTextWidth = right - left;
			String msg = TextUtils.ellipsize(subTitleText, textPaint,
					availableTextWidth , TextUtils.TruncateAt.END).toString();


			Log.d(TAG , "=====subTitleText measureText======");
			float textWidth = paint.measureText(msg);
			Log.d(TAG , "=====subTitleText measureText======");
			/**
			 * 如果串宽度小于控件宽度,就距中开位置开始画,不然就从头开始画
			 */
			float x = textWidth < (right - left) ? left
					+ (right - left - textWidth) / 2 : left;
			canvas.drawText(msg, x, bottom - fm.descent, paint);

			
			/**
			 * 计算画完副标题后重新计算新的底部高度
			 */
			rect.bottom -= (textHeight + subTitlePaddingTop + subTitlePaddingBottom);
		}

		if (titleText != null) {
			paint.setTextSize(titleTextSize);
			paint.setColor(titleTextColor);
			paint.setTextAlign(Paint.Align.LEFT);

			FontMetrics fm = paint.getFontMetrics();
			int textHeight = (int) Math.ceil(fm.descent - fm.ascent);

			float left = getPaddingLeft() + titlePaddingLeft;
			float right = mViewWidth - getPaddingRight() - titlePaddingRight;
			float bottom = rect.bottom - titlePaddingBottom;

			String msg = TextUtils.ellipsize(titleText, textPaint,
					right - left, TextUtils.TruncateAt.END).toString();


			Log.d(TAG , "=====titleText measureText======");
			float textWidth = paint.measureText(msg);
			Log.d(TAG , "=====titleText measureText======");
			
			float x = textWidth < right - left ? left
					+ (right - left - textWidth) / 2 : left;
			canvas.drawText(msg, x, bottom - fm.descent, paint);

			rect.bottom -= (textHeight + titlePaddingTop + titlePaddingBottom);
		}

		if (imageBitmap != null) {
			paint.setAlpha((int) (255 * imageAlpha));
			
			/**
			 * 可见http://www.cnblogs.com/nikyxxx/archive/2012/06/28/2568726.html
			 */
			rect.left += imagePaddingLeft;//要绘制的图,就直接使用padding来界定图的显示范围
			rect.top += imagePaddingTop;
			rect.right -= imagePaddingRight;
			rect.bottom -= imagePaddingBottom;
			if (imageScaleType == SCALE_TYPE_FILLXY) {
				canvas.drawBitmap(imageBitmap, null, rect, paint);
			} else if (imageScaleType == SCALE_TYPE_CENTER) {
				int bw = imageBitmap.getWidth();
				int bh = imageBitmap.getHeight();
				if (bw < rect.right - rect.left) {
					int delta = (rect.right - rect.left - bw) / 2;
					rect.left += delta;
					rect.right -= delta;
				}
				if (bh < rect.bottom - rect.top) {
					int delta = (rect.bottom - rect.top - bh) / 2;
					rect.top += delta;
					rect.bottom -= delta;
				}
				canvas.drawBitmap(imageBitmap, null, rect, paint);//这个方法  第一个参数是图片原来的大小,第二个参数是 绘画该图片需显示多少。也就是说你想绘画该图片的某一些地方,而不是全部图片,第三个参数表示该图片绘画的位置
			}
		}
	}

 

 


    当做完这一步的时候,我们的自定义控件已经能够在布局文件中进行使用了,但是我们还不能在AdapterView中用我们设计的布局文件,因为AdapterView中每一个item属性都是在java代码中动态设置的,因此我们就需要给我们的自定义控件开放属性设置的接口,我们这里暂时只开放了设置图片和文字内容的接口。

 

 

	public void setImageBitmap(Bitmap bitmap) {
		imageBitmap = bitmap;
		requestLayout();
		invalidate();
	}
	
	public void setTitleText(String text) {
		titleText = text;
		requestLayout();
		invalidate();
	}
	
	public void setSubTitleText(String text) {
		subTitleText = text;
		requestLayout();
		invalidate();
	}

  做到这一步的时候,这个自定义控件基本就算完成了,后续的工作就是一些完善和修补了。

 

 

 

    接下来就是自定义控件的使用了,在布局文件中使用自定义控件的时候我们需要额外做一点工作,如下:

 

<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:carrey="http://schemas.android.com/apk/res/com.carrey.customview"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.carrey.customview.customview.CustomView 
        android:id="@+id/customview"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:background="#FFD700"
        carrey:imageSrc="@drawable/clock"
        carrey:imageAspectRatio="1.0"
        carrey:imageAlpha="0.5"
        carrey:imagePaddingLeft="5dp"
        carrey:imagePaddingTop="5dp"
        carrey:imagePaddingRight="5dp"
        carrey:imagePaddingBottom="5dp"
        carrey:imageScaleType="center"
        carrey:titleText="这是一级标题"
        carrey:titleTextSize="30sp"
        carrey:titleTextColor="#1E90FF"
        carrey:titlePaddingLeft="4dp"
        carrey:titlePaddingTop="4dp"
        carrey:titlePaddingRight="4dp"
        carrey:titlePaddingBottom="4dp"
        carrey:subTitleText="这是二级子标题"
        carrey:subTitleTextSize="20sp"
        carrey:subTitleTextColor="#00FF7F"
        carrey:subTitlePaddingLeft="3dp"
        carrey:subTitlePaddingTop="3dp"
        carrey:subTitlePaddingRight="3dp"
        carrey:subTitlePaddingBottom="3dp"/>

    <Button 
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="next page"/>
</RelativeLayout>

 

 

 

 

我们需要添加一行xmlns:carrey="http://schemas.android.com/apk/res/com.carrey.customview",其中carrey是一个前缀,你可以随意设置,com.carrey.customview是我们的应用的包名,如果拿不准的可以打开Manifest文件,在<manifest>节点中找到package属性值即可。
对于在AdapterView中的使用方法就和我们正常使用一个常用控件的方法是一样的,这里就不赘述了,如果说到了这里还有一些不明白的地方,可以下载我下面提供的源码(源码注释更多一点),然后对照着博客的思路来看,或者给我留言进行交流。

 

 

 

 

分享到:
评论

相关推荐

    自定义控件与属性

    下面我们将深入探讨如何利用Java代码自定义控件,以及如何进行属性设置。 1. **自定义控件基础** 自定义控件通常通过继承已有的View或ViewGroup类来实现。例如,如果你想要创建一个自定义按钮,你可以从Button类...

    Android自定义控件开发入门与实战.zip

    2. 设计UI布局:在XML布局文件中声明自定义控件,并设置相关属性。 3. 处理事件:覆盖onTouchEvent()或其他事件处理方法,实现对用户交互的响应。 二、自定义属性 Android提供了AttributeSet接口,允许自定义控件...

    android自定义控件及其属性设置

    2. 构造函数:为控件提供初始化逻辑,通常包括设置默认属性等。 ```java public MySelfView(Context context) { super(context); } public MySelfView(Context context, @Nullable AttributeSet attrs) { super...

    自定义控件 - 设计模式 - 良好设计 - ViewPager

    "自定义控件 - 设计模式 - 良好设计 - ViewPager"这个主题,旨在探讨如何利用设计模式来创建高效且可维护的自定义ViewPager。ViewPager是一个常用的Android组件,它允许用户在多个视图之间滑动,常用于实现页面轮播...

    自定义控件Demo

    通过自定义控件,开发者可以创建出符合自身需求的UI组件,从而更好地实现应用的功能和设计。本Demo着重介绍了如何在Android中创建自定义控件,并提供了相关的源码供学习和参考。 一、自定义控件的基本步骤 1. 创建...

    安卓自定义控件相关-Android自定义控件源码.rar

    这个压缩包"Android自定义控件源码.rar"包含了一些自定义控件的源代码,虽然不能保证每个都可直接运行,但它们提供了丰富的学习资源,帮助开发者理解和实践自定义控件的创建过程。下面将详细探讨Android自定义控件的...

    安卓自定义控件相关-自定义控件入门级demo.zip

    2. **重写测量与布局**:为了使自定义控件适应不同的屏幕尺寸和布局,你需要重写onMeasure()方法来确定控件的大小,并可能需要重写onLayout()方法来确定控件的位置。MeasureSpec是Android系统用来传递父视图对子视图...

    自定义控件遇到的两个小问题

    此外,理解View的测量(onMeasure())、布局(onLayout())和绘制(onDraw())流程也对优化自定义控件的性能有很大帮助。 在博客“自定义控件遇到的俩个小问题”中,作者可能详细分析了这些问题的解决方案,包括...

    【Android进阶】(1)用继承和组合方式自定义控件

    在Android开发中,自定义控件是提升应用用户体验和界面设计独特性的重要手段。本教程主要探讨如何通过继承和组合的方式来自定义控件,适用于已经有一定Android基础的开发者进行进阶学习。 首先,我们来理解自定义...

    自定义控件实例源码

    在这个目录下,我们可以期待找到一些具体的自定义控件实现,例如自定义的按钮、滑块、进度条等,这些控件可能包含了独特的动画效果或者交互方式。通过阅读和分析这些源码,我们可以学习到如何定义控件的布局XML、在...

    自定义控件入门集

    在Android开发中,自定义控件是提升应用界面独特性和用户体验的...在实际的项目开发中,自定义控件的应用无处不在,无论是为了满足特殊的交互需求,还是为了实现独特的视觉设计,都是提升应用品质不可或缺的一部分。

    android自定义控件+自定义属性的使用

    在Android开发中,自定义控件和自定义属性是提升应用独特性和功能扩展性的重要手段。自定义控件允许开发者根据需求创建具有特定功能或视觉效果的组件,而自定义属性则能让这些控件更加灵活,能够适应各种场景。下面...

    自定义控件的方法

    4. **测量尺寸**:自定义控件时,可能需要重写onMeasure()方法,根据MeasureSpec来计算控件的大小。正确地测量尺寸是保证布局正确显示的关键。 5. **布局更新**:如果自定义控件是ViewGroup,还需要处理子视图的...

    自定义控件其实很简单7/12

    综上所述,自定义控件涉及了Android开发中的多个方面,从基础的视图绘制到复杂的交互处理,再到性能优化和用户体验设计。通过不断实践和学习,开发者能够熟练掌握这一技能,为应用增添独特的功能和视觉效果。...

    Android自定义控件示例

    9. **性能优化**:自定义控件需要关注性能问题,如避免在`onDraw()`中执行耗时操作,合理使用`invalidate()`和`postInvalidate()`刷新视图,以及优化测量和布局过程。 10. **测试和调试**:使用Android Studio的...

    android自定义控件介绍,重写控件

    此外,熟练运用属性动画和视图状态等高级特性,可以提升自定义控件的交互性和视觉效果。 总之,Android自定义控件是提升应用品质的关键技能。通过重写和扩展标准控件,我们可以创造出独具特色的用户界面,满足用户...

    自定义控件项目含多个

    自定义控件允许开发者根据需求创建具有特定功能或视觉效果的组件,从而更好地满足应用的设计与交互需求。"自定义控件项目含多个"表明这个压缩包可能包含一系列自定义控件的源代码示例,每个控件都解决了一种特定的...

    自定义控件其实很简单1/4

    1. `onDraw()`:这是自定义控件的核心,负责绘制控件的视觉表现。你可以使用`Canvas`对象来绘制线条、形状、文本等。例如,使用`canvas.drawCircle()`画一个圆,`canvas.drawText()`写入文本。 2. `onMeasure()`:...

    自定义控件其实很简单5/12

    在Android开发中,自定义控件...总之,自定义控件是Android开发中的重要技能,它涉及到UI设计、事件处理、动画等多个方面。通过深入学习和实践,开发者不仅可以打造独特的应用界面,还能提升编程技巧和解决问题的能力。

Global site tag (gtag.js) - Google Analytics