精华内容
参与话题
问答
  • Android 自定义View (一)

    万次阅读 多人点赞 2014-04-21 15:20:04
    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤: 1、自定义View的属性 2、...

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901

    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤:

    1、自定义View的属性

    2、在View的构造方法中获得我们自定义的属性

    [ 3、重写onMesure ]

    4、重写onDraw

    我把3用[]标出了,所以说3不一定是必须的,当然了大部分情况下还是需要重写的。

    1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <attr name="titleText" format="string" />
        <attr name="titleTextColor" format="color" />
        <attr name="titleTextSize" format="dimension" />
    
        <declare-styleable name="CustomTitleView">
            <attr name="titleText" />
            <attr name="titleTextColor" />
            <attr name="titleTextSize" />
        </declare-styleable>
    
    </resources>
    我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:

    一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;不清楚的可以google一把。

    然后在布局中声明我们的自定义View

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.customview01.view.CustomTitleView
            android:layout_width="200dp"
            android:layout_height="100dp"
            custom:titleText="3712"
            custom:titleTextColor="#ff0000"
            custom:titleTextSize="40sp" />
    
    </RelativeLayout>

    一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"我们的命名空间,后面的包路径指的是项目的package

    2、在View的构造方法中,获得我们的自定义的样式

    /**
    	 * 文本
    	 */
    	private String mTitleText;
    	/**
    	 * 文本的颜色
    	 */
    	private int mTitleTextColor;
    	/**
    	 * 文本的大小
    	 */
    	private int mTitleTextSize;
    
    	/**
    	 * 绘制时控制文本绘制的范围
    	 */
    	private Rect mBound;
    	private Paint mPaint;
    
    	public CustomTitleView(Context context, AttributeSet attrs)
    	{
    		this(context, attrs, 0);
    	}
    
    	public CustomTitleView(Context context)
    	{
    		this(context, null);
    	}
    
    	/**
    	 * 获得我自定义的样式属性
    	 * 
    	 * @param context
    	 * @param attrs
    	 * @param defStyle
    	 */
    	public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
    	{
    		super(context, attrs, defStyle);
    		/**
    		 * 获得我们所定义的自定义样式属性
    		 */
    		TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
    		int n = a.getIndexCount();
    		for (int i = 0; i < n; i++)
    		{
    			int attr = a.getIndex(i);
    			switch (attr)
    			{
    			case R.styleable.CustomTitleView_titleText:
    				mTitleText = a.getString(attr);
    				break;
    			case R.styleable.CustomTitleView_titleTextColor:
    				// 默认颜色设置为黑色
    				mTitleTextColor = a.getColor(attr, Color.BLACK);
    				break;
    			case R.styleable.CustomTitleView_titleTextSize:
    				// 默认设置为16sp,TypeValue也可以把sp转化为px
    				mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
    						TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
    				break;
    
    			}
    
    		}
    		a.recycle();
    
    		/**
    		 * 获得绘制文本的宽和高
    		 */
    		mPaint = new Paint();
    		mPaint.setTextSize(mTitleTextSize);
    		// mPaint.setColor(mTitleTextColor);
    		mBound = new Rect();
    		mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
    
    	}

    我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

    3、我们重写onDraw,onMesure调用系统提供的:

    @Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    	{
    		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	}
    
    	@Override
    	protected void onDraw(Canvas canvas)
    	{
    		mPaint.setColor(Color.YELLOW);
    		canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
    
    		mPaint.setColor(mTitleTextColor);
    		canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    	}
    此时的效果是:

    是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:


    系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

    所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:

    重写之前先了解MeasureSpec的specMode,一共三种类型:

    EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

    AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

    UNSPECIFIED:表示子布局想要多大就多大,很少使用

    下面是我们重写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);
    		int width;
    		int height ;
    		if (widthMode == MeasureSpec.EXACTLY)
    		{
    			width = widthSize;
    		} else
    		{
    			mPaint.setTextSize(mTitleTextSize);
    			mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
    			float textWidth = mBounds.width();
    			int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
    			width = desired;
    		}
    
    		if (heightMode == MeasureSpec.EXACTLY)
    		{
    			height = heightSize;
    		} else
    		{
    			mPaint.setTextSize(mTitleTextSize);
    			mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
    			float textHeight = mBounds.height();
    			int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
    			height = desired;
    		}
    		
    		
    
    		setMeasuredDimension(width, height);
    	}
    

    现在我们修改下布局文件:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.example.customview01.view.CustomTitleView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            custom:titleText="3712"
            android:padding="10dp"
            custom:titleTextColor="#ff0000"
            android:layout_centerInParent="true"
            custom:titleTextSize="40sp" />
    
    </RelativeLayout>

    现在的效果是:


    完全复合我们的预期,现在我们可以对高度、宽度进行随便的设置了,基本可以满足我们的需求。

    当然了,这样下来我们这个自定义View与TextView相比岂不是没什么优势,所有我们觉得给自定义View添加一个事件:

    在构造中添加:

    this.setOnClickListener(new OnClickListener()
    		{
    
    			@Override
    			public void onClick(View v)
    			{
    				mTitleText = randomText();
    				postInvalidate();
    			}
    
    		});

    private String randomText()
    	{
    		Random random = new Random();
    		Set<Integer> set = new HashSet<Integer>();
    		while (set.size() < 4)
    		{
    			int randomInt = random.nextInt(10);
    			set.add(randomInt);
    		}
    		StringBuffer sb = new StringBuffer();
    		for (Integer i : set)
    		{
    			sb.append("" + i);
    		}
    
    		return sb.toString();
    	}

    下面再来运行:


    我们添加了一个点击事件,每次让它随机生成一个4位的随机数,有兴趣的可以在onDraw中添加一点噪点,然后改写为验证码,是不是感觉很不错。


    好了,各位学习的,打酱油的留个言,顶个呗~


    源码点击此处下载






    展开全文
  • android 自定义view

    千次阅读 2016-12-21 10:22:22
    文章汇总: Android自定义控件三部曲文章索引 ANDROID-自定义控件-继承VIEW与VIEWGROUP的初步理解 Android 自定义View合集
    展开全文
  • Android 自定义View

    千次阅读 2016-08-15 21:17:49
    很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路。这里老鸟抛砖引玉一下,总结下自定义View的步骤: 1、自定义View的属性 2、在View的构造方法中获得我们...

    Android 开发必然会面对自定义View, 因为许多时候android sdk里现有的View 并不能满足我们项目的需求。很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路。我抛砖引玉一下,总结下自定义View的步骤:

    1、自定义View的属性

    2、在View的构造方法中获得我们自定义的属性

    3、重写onMesure方法

    4、重写onLayout方法

    5、重写onDraw方法

    其实上面的几个步骤都是非必需同时需要的,完全可以根据自己具体的需要而定。比如,如果你实现了上面1,2步,那么你的自定义view就能像android原生View一样在xml布局文件里使用一些自定义的属性,然后结合你在自己view里面对这些属性的处理,达到控制你的view的目的。onMesure方法里面用来测试和决定你的自定义View的宽高,当然如果你的自定义view是一个ViewGroup,即里面还包含子view, 则在onMesure里面是测量子View宽高,以决定自己宽高的绝佳地方。onlayout方法是父类决定子view在其父类中的位置的地方,所以如果你的view里面没有子view,当然就不需要重写此方法。而onDraw方法是你需要手动绘制一些图案或文字的地方。

    在具体举例自定义view前,先看一下我自定义view的效果:


    大家都知道这两天最火的莫过于王宝强离婚事件了,甚至比同期的奥运事件还火,这里我们定义一个能在图片上添加自定义标签的功能View。如上图,我们在上图中添加了两个标签,要实现上图功能,我们可以分两步:

    一,自定义一个标签View,我们这里叫做LabelView;

    二,自定义一个图片上能添加LabelView的View,我们这里叫做LabelImageView。

    这里LabelView有两种样式,即上图的单行模式和两行模式。

    第一步,自定义View的属性。我们先在app的value目录下定义一个label_view_attrs.xml文件,用于控制LabelView的一些有用的属性:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="LabelView">
            <!--标签第一行文字-->
            <attr name="labelText1" format="string" />
            <!--标签第二行文字-->
            <attr name="labelText2" format="string"/>
            <!--标签文字的颜色-->
            <attr name="labelTextColor" format="color"/>
            <!--标签文字的大小-->
            <attr name="labelTextSize" format="dimension"/>
            <!--文字下划线的颜色-->
            <attr name="labelLineColor" format="color"/>
            <!--文字下划线的大小-->
            <attr name="labelLineSize" format="dimension"/>
            <!--标签头部的颜色-->
            <attr name="labelHeaderColor" format="color"/>
            <!--标签头部的半径-->
            <attr name="labelHeaderRadius" format="dimension"/>
            <!--标签文字的方向,即文字是在标签头的左边还是右边-->
            <attr name="labelDirect" >
                <enum name="right" value="0"/>
                <enum name="left" value="1"/>
            </attr>
        </declare-styleable>
    </resources>
    


    第二步,在View的构造方法中获得我们自定义的属性。新建LaberView.java文件,这就是我们的自定义View实现的代码,它必须要继承View或其他现有的View,这里我们继承View就可以了。而在LaberView的构造函数里我们就可以获取上面label_view_attrs.xml的属性

     public LabelView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            //定义一个标签数据管理者
            mLabelDataManager = new LabelDataManager(context);
            /**
             * 获得我们所定义的自定义样式属性
             */
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LabelView, defStyleAttr, 0);
            int n = a.getIndexCount();
            for (int i = 0; i < n; i++)
            {
                int attr = a.getIndex(i);
                switch (attr)
                {
                    case R.styleable.LabelView_labelText1:
                        mLabelDataManager.setLabelText1(a.getString(attr));
                        break;
                    case R.styleable.LabelView_labelText2:
                        mLabelDataManager.setLabelText2(a.getString(attr));
                        break;
                    case R.styleable.LabelView_labelTextColor:
                        mLabelDataManager.mLabelTextColor =a.getColor(attr, Color.WHITE);
                        break;
                    case R.styleable.LabelView_labelTextSize:
                        // 默认设置为16sp
                        int labelTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                                TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                        mLabelDataManager.mLabelTextSize = labelTextSize;
                        break;
                    case R.styleable.LabelView_labelLineColor:
                        mLabelDataManager.mLabelLineColor = a.getColor(attr,Color.WHITE);
                        break;
                    case R.styleable.LabelView_labelLineSize:
                        mLabelDataManager.mLabelLineSize = a.getDimensionPixelSize(attr,2);
                        break;
                    case R.styleable.LabelView_labelHeaderColor:
                        mLabelDataManager.mLabelHeaderColor = a.getColor(attr,Color.WHITE);
                        break;
                    case R.styleable.LabelView_labelHeaderRadius:
                        mLabelDataManager.mLabelHeaderRadius = a.getDimensionPixelSize(attr,7);
                        mLabelDataManager.setLabelHeaderShaderRadius(mLabelDataManager.mLabelHeaderRadius +6);
                        break;
                    case R.styleable.LabelView_labelDirect:
                        mLabelDirection = a.getInt(attr,RIGHT_LABEL);
                        break;
                }
    
            }
            //回收内存
            a.recycle();
            mLabelDataManager.build();
        }
    这里我定义了一个LabelDataManager对象,负责管理计算这些属性变量。通过上面代码我们就可以在xml里面获得我们设置的属性值,比如文字的大小,颜色等。于是xml的布局文件里我们就可以如下使用我们的LabelImageView了:

     <app.study.nick.com.demo.view.LabelView
            xmlns:labelview="http://schemas.android.com/apk/res-auto"
            android:id="@+id/label4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:layout_centerInParent="true"
            android:padding="4dp"
            labelview:labelText1="宝宝不哭"
            labelview:labelText2="宝宝,顶顶顶顶"
            labelview:labelTextColor="@color/white"
            labelview:labelTextSize="12sp"
            labelview:labelLineColor="@color/white"
            labelview:labelLineSize="2dp"
            labelview:labelHeaderColor="@color/white"
            labelview:labelHeaderRadius="5dp"
            labelview:labelDirect="right" />


    第三步,重写onMesure方法。onMesure方法用于设置View的宽高,因为LabelView是我们自己绘制出来的,所以具体需要多宽多高,我们需要计算出来告诉系统。

       //EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
        //AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
        //UNSPECIFIED:表示子布局想要多大就多大,很少使用
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            int reallyWidth;
            int reallyHeight;
    
            if (widthMode == MeasureSpec.EXACTLY)
            {
                //如果设置了固定的宽度,就用这个固定的宽度
                reallyWidth = widthSize;
            } else {
                //没有固定宽度,就自己计算Label所要占多宽,再加上两边padding
                reallyWidth = getPaddingLeft() + mLabelDataManager.getLabelTotalWidth() + getPaddingRight();
            }
    
            if (heightMode == MeasureSpec.EXACTLY)
            {
                //如果设置了固定的高度,就用这个固定的高度
                reallyHeight = heightSize;
            } else {
                //没有固定高度,就自己计算Label所要占多高,再加上两边padding
                reallyHeight = getPaddingTop() + mLabelDataManager.getLabelTotalHeight() + getPaddingBottom();
            }
            //设置宽高
            setMeasuredDimension(reallyWidth,reallyHeight);
        }

    onMeasure方法里最需要注意的就是MeasureSpec的三种模式了,EXACTLY, AT_MOST还有UNSPECIFIED三种模式,它是根据你在xml布局文件里的宽高设置决定的,除却UNSPECIFIED不谈,其他两种mode:当父布局是EXACTLY时,子控件确定大小或者match_parent,mode都是EXACTLY,子控件是wrap_content时,mode为AT_MOST;当父布局是AT_MOST时,子控件确定大小,mode为EXACTLY,子控件wrap_content或者match_parent时,mode为AT_MOST。所以在确定控件大小时,需要判断MeasureSpec的mode,不能直接用MeasureSpec的size。在进行一些逻辑处理以后,调用setMeasureDimension()方法,将测量得到的宽高传进去供layout使用。

    需要明白的一点是 ,测量所得的宽高不一定是最后展示的宽高,最后宽高确定是在onLayout方法里,layou(left,top,right,bottom),不过一般都是一样的。


    第四步,重写onLayout方法。这里我们不需要重写onLayout方法,因为LabelView只是一个单纯的view,它不是一个view容器,没有子view,而onLayout方法里主要是具体摆放子view的位置,水平摆放或者垂直摆放,所以在单纯的自定义view是不需要重写onLayout方法,不过需要注意的一点是,子view的margin属性是否生效就要看parent是否在自身的onLayout方法进行处理,而view的padding属性是需要自己在onDraw方法中处理生效的。


    第五步,重写onDraw方法。onDraw 是自定义view的重头戏,一般自定义控件耗费心思最多的就是这个方法了,需要在这个方法里,用Paint在Canvas上画出你想要的图案,这样一个自定义view才算结束。

      @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //画标签的下划线
            mLabelDataManager.drawLabelLine(canvas);
            //画标签的文字
            mLabelDataManager.drawLabelText(canvas);
            //画标签的头部阴影
            mLabelDataManager.drawLabelHeaderShader(canvas);
            //画标签的头部圆
            mLabelDataManager.drawLabelHeader(canvas);
        }
    
       public void drawLabelHeader(Canvas canvas){
                //画圆
                canvas.drawCircle(mLabelHeaderCenterPoint.x,mLabelHeaderCenterPoint.y,mLabelHeaderRadius,mHeaderPaint);
            }
    
            public void drawLabelHeaderShader(Canvas canvas){
                //设置阴影
                Shader mShader = new RadialGradient((float) mLabelHeaderCenterPoint.x,(float) mLabelHeaderCenterPoint.y,
                        (float) mLabelHeaderShaderRadius,0x7f000000,0x3f000000,Shader.TileMode.CLAMP);
                mHeaderShaderPaint.setShader(mShader);
                canvas.drawCircle(mLabelHeaderCenterPoint.x,mLabelHeaderCenterPoint.y,mLabelHeaderShaderRadius,mHeaderShaderPaint);
            }
    
            public void drawLabelLine(Canvas canvas){
                if(mLabelLinePath1 != null){
                    //画路径
                    canvas.drawPath(mLabelLinePath1,mLinePaint);
                }
            }
    
            public void drawLabelText(Canvas canvas){
                if(mLabelText1StartPoint != null){
                    //画文字
                    canvas.drawText(getLabelText1(),mLabelText1StartPoint.x,mLabelText1StartPoint.y,mTextPaint);
                }
                if(mLabelText2StartPoint != null){
                    canvas.drawText(getLabelText2(),mLabelText2StartPoint.x,mLabelText2StartPoint.y,mTextPaint);
                }
            }

    onDraw方法其实就是调用canvas的一些绘制各种图案的方法而已。所以最费心思的就是你需要去计算出你要绘制的各图案的起始和结束位置而已。下面我们看看效果:



    到这里自定义一个LabelView就已经基本完成了,是不是很简单。如果你想让上面的标签头动起来,怎么办呢?用invalidate()方法还是用postInvalidate()方法或是requestLayout()方法呢?

    requestLayout: 当我们调用requestLayout的时候,从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程。但是,真实情况略有不同,如果子View调用了这个方法,其实会从View树重新进行一次测量、布局、绘制这三个流程,最终就会显示子View的最终情况。

    invalidate:当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到根View中,最终进行开始View树重绘流程(只绘制需要重绘的视图)。

    postInvalidate:这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。postInvalidate其实是内部通过handler给主线程发送更新view消息而已。


     需要注意的是,onDraw方法是运行于主UI线程中的,如果你在onDraw中执行invalidate()方法去更新屏幕,是可以的。但是你既要继承View而且要不希望堵塞主UI线程的话,可以另外新建线程,然后在线程中执行postInvalidate()方法去更新屏幕。也就是说invalidate()方法只能在主UI线程中被调用,postInvalidate()方法只能在非主UI线程中被调用。
    先看看添加动画后的效果:

    这个动画还是比较和谐好看的,下面看看代码如何实现:
     public void startAnimation() {
            mAnimator = ValueAnimator.ofFloat(0.4f, 1);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float scale = (float) animation.getAnimatedValue();
                    //改变标签圆半径
                    mLabelDataManager.changeLabelHeaderRaduis(scale);
                    //请求重绘
                    postInvalidate();
                }
            });
            //设置动画持续时间
            mAnimator.setDuration(1000);
            //设置动画的动作,先加速后减速
            mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            // 重复次数 -1表示无限循环
            mAnimator.setRepeatCount(-1);
            // 重复模式, RESTART: 重新开始 REVERSE:恢复初始状态再开始
            mAnimator.setRepeatMode(ValueAnimator.REVERSE);
            //启动动画
            mAnimator.start();
        }
    这里利用了android系统自带的ValueAnimator动画,来实现循环缩放标签头部圆。这里需要注意的是,如果更多动画效果还是不建议用自定义View实现,因为自定义View的onDraw执行在UI线程中,会占用UI线程,过多复杂动画会导致APP卡顿,这时候可以使用自定义SurfaceView代替View,因为surfaceView的绘图是可以在其他线程中运行的,不占用UI线程。

    关于自定义LabelImageView我会在下一篇讲解。更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号: Android老鸟

                                                    



    展开全文
  • android 自定义View的几种方式

    千次阅读 2020-07-18 10:23:42
    本文主要总结一下笔者实际项目中碰到的自定义View的几种方式,以及优劣。 大概有以下几种方式: 使用时绑定 不使用xml,直接java实现 一次定义,随处可用 本文更多的还是个人使用经验的总结,如有错误或者需要...

    概述

    本文主要总结一下笔者实际项目中碰到的自定义View的几种方式,以及优劣。

    大概有以下几种方式:

    1. 使用时绑定
    2. 不使用xml,直接java实现
    3. 一次定义,随处可用

    本文更多的还是个人使用经验的总结,如有错误或者需要补充的地方,欢迎评论交流。

    Demo

    demo中分别使用三种方式实现了类似的布局。除了作者将列出的优劣之外,读者可自行查看代码来进行比较。
    实现效果与项目文件如下:

    一、使用时绑定

    优势

    1. 页面布局与View的业务逻辑解耦。
    2. 页面布局使用xml实现,可以预览,在页面较复杂的情况下,可以很大的提交开发者的效率。

    劣势

    1. 此方式实现的自定义View无法直接在xml中调用。

    适合场景
    不适合需要在xml中直接调用的场景,其他大部分场景都可以适用。

    TestViewOne.java

    public class TestViewOne extends LinearLayout {
      //ui
      private TextView tvTitle;
      private TextView tvMessage;
    
      public TestViewOne(Context context) {
        this(context, null);
      }
    
      public TestViewOne(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
      }
    
      public TestViewOne(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
      }
    
      @Override protected void onFinishInflate() {
        super.onFinishInflate();
        bindView();
      }
    
      private void bindView() {
        tvTitle = findViewById(R.id.tv_title_view_one);
        tvMessage = findViewById(R.id.tv_message_view_one);
      }
    
      public void setTitle(String title) {
        tvTitle.setText(title);
      }
    
      public void setMessage(String message) {
        tvMessage.setText(message);
      }
    }
    

    view_test_one.xml

    <?xml version="1.0" encoding="utf-8"?>
    <com.example.multiviewtest.TestViewOne xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="center_vertical"
        android:orientation="vertical"
        android:padding="20dp"
        >
      <TextView
          android:id="@+id/tv_title_view_one"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textSize="30sp"
          tools:text="@string/test_view_title"
          />
    
      <TextView
          android:id="@+id/tv_message_view_one"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textSize="20sp"
          tools:text="@string/test_view_message"
          />
    
    </com.example.multiviewtest.TestViewOne>
    

    二、不使用xml,直接java实现

    优势

    1. 不需要单独实现xml,如果页面布局不复杂的情况下,可以说是简化的代码的逻辑。

    劣势

    1. 不用xml实现就无法预览,在页面较复杂的情况下,会降低开发的效率。
    2. 耦合了页面布局和业务逻辑。

    适合场景
    适用于页面布局不复杂的情况。
    也适用于给原生的控件增加拓展的场景,比如想给原生的LinearLayout增加header。

    TestViewTwo.java

    public class TestViewTwo extends LinearLayout {
    
      //data
      private Context context;
      //ui
      private TextView tvTitle;
      private TextView tvMessage;
    
      public TestViewTwo(Context context) {
        this(context, null);
      }
    
      public TestViewTwo(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
      }
    
      public TestViewTwo(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    
        this.context = context;
    
        int padding = DisplayUtils.dpToPx(context, 20);//20dp
        setOrientation(VERTICAL);
        setPadding(padding, padding, padding, padding);
        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, DisplayUtils.dpToPx(context, 150)));
    
        createChildView();
      }
    
      private void createChildView() {
        tvTitle = new TextView(this.context);
        tvTitle.setTextSize(30);
        tvTitle.setLayoutParams(
            new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        this.addView(tvTitle);
    
        tvMessage = new TextView(this.context);
        tvMessage.setTextSize(20);
        tvMessage.setLayoutParams(
            new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        this.addView(tvMessage);
      }
    
      public void setTitle(String title) {
        tvTitle.setText(title);
      }
    
      public void setMessage(String message) {
        tvMessage.setText(message);
      }
    }
    

    三、一次定义,随处可用

    优势

    1. 将大部分页面布局的逻辑 与 View的业务逻辑解耦。
    2. 页面布局使用xml实现,可以预览,在页面较复杂的情况下,可以很大的提交开发者的效率。

    劣势

    1. 与方案一相比,需要在非xml中实现父布局的内容。还是将一部分页面布局的逻辑与View的业务逻辑耦合了。

    适合场景
    适用于所有场景,尤其是页面布局复杂的场景。
    (与方案一相比,这种方案也可以再xml中直接调用)

    这种方案中,xml中的最外层布局一定要使用merge,否则会增加一层毫无意义的嵌套。
    将原来最外层的布局改成merge后,会发现原来的布局无法预览了。此时,给merge加上“tools:parentTag”这个参数就可以了。

    TestViewThree.java

    public class TestViewThree extends LinearLayout {
      //ui
      private TextView tvTitle;
      private TextView tvMessage;
    
      public TestViewThree(Context context) {
        this(context, null);
      }
    
      public TestViewThree(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
      }
    
      public TestViewThree(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    
        LayoutInflater.from(context).inflate(R.layout.view_test_three, this, true);
    
        int padding = DisplayUtils.dpToPx(context, 20);//20dp
        setOrientation(VERTICAL);
        setPadding(padding, padding, padding, padding);
        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, DisplayUtils.dpToPx(context, 150)));
    
        initViews();
      }
    
      private void initViews() {
        tvTitle = findViewById(R.id.tv_title_view_one);
        tvMessage = findViewById(R.id.tv_message_view_one);
      }
    
      public void setTitle(String title) {
        tvTitle.setText(title);
      }
    
      public void setMessage(String message) {
        tvMessage.setText(message);
      }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="center_vertical"
        android:orientation="vertical"
        android:padding="20dp"
        tools:parentTag="android.widget.LinearLayout"
        >
      <TextView
          android:id="@+id/tv_title_view_one"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textSize="30sp"
          tools:text="@string/test_view_title"
          />
    
      <TextView
          android:id="@+id/tv_message_view_one"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textSize="20sp"
          tools:text="@string/test_view_message"
          />
    
    </merge>
    

    其他文件代码

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
      //ui
      private LinearLayout llRoot;
      private TestViewOne testViewOne;
      private TestViewTwo testViewTwo;
      private TestViewThree testViewThree;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        initViews();
      }
    
      private void initViews() {
        llRoot = findViewById(R.id.ll_root_main);
    
        testViewTwo = findViewById(R.id.tv_test_two_main);
        testViewTwo.setTitle("test two title");
        testViewTwo.setMessage("test two message");
    
        testViewThree = findViewById(R.id.tv_test_three_main);
        testViewThree.setTitle("test three title");
        testViewThree.setMessage("test three message");
    
        testViewOne =
            (TestViewOne) LayoutInflater.from(this).inflate(R.layout.view_test_one, null, false);
        testViewOne.setTitle("test one title");
        testViewOne.setMessage("test one message");
        llRoot.addView(testViewOne);
      }
    }
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/ll_root_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity"
        >
      <com.example.multiviewtest.TestViewTwo
          android:id="@+id/tv_test_two_main"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          />
      <com.example.multiviewtest.TestViewThree
          android:id="@+id/tv_test_three_main"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          />
    
    </LinearLayout>
    

    DisplayUtils.java

    由于实现中会存在单位的差异,因此单独定义了这个工具类来负责单位转化。

    public class DisplayUtils {
      public static int dpToPx(Context context, float pxValue) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue * scale);
      }
    }
    

    总结

    个人最推荐方案三,原因有以下几点:

    1. 除非是非常简单的布局,不然都推荐使用xml来做。用xml实现可预览的布局是AS的一个很实用的特性,不管是开发新功能还是bugfix都能极大的提高效率。
    2. 页面布局可以和业务逻辑解耦。
    3. 在java文件和xml中都可以直接调用,适用性最广。
    展开全文
  • Android 自定义View (二) 进阶

    万次阅读 多人点赞 2014-04-22 11:39:25
    继续自定义View之旅,前面已经介绍过一个自定义View的基础的例子,Android 自定义View (一),如果你还对自定义View不了解可以去看看。今天给大家带来一个稍微复杂点的例子。 自定义View显示一张图片,下面包含图片...
  • Android自定义View-自定义Spinner 在使用系统Spinner时,往往总不能跟自己主题进行颜色搭配。这里介绍一下对Spinner的样式的修改,主要是对外观和颜色的修改。 效果图: Spinner背景图片: 1. 2. 效果图中...
  • 关于Android 自定义View中的onDraw里的drawBitmap 有一个半圆的图片,是一个统计图(就是油箱表的那样),还有一个指针的图片要把指针添加到图片里,然后传值让那个指针根据而移动。 找不到什么方法,所以求教下...
  • 自定义View 为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.自定义View 我们大部分时候只需重写两个函数: onMeasure(),onDraw(). ...
  • Android自定义View 之自定义属性

    千次阅读 2019-03-28 16:53:15
    自定义view的起步是自定义属性,并且正确的读取属性。 在res/values/attrs.xml的文件中创建属性: <declare-styleable name="ViewDemoAttr35"> <attr name="string35" format="string"/> <attr ...
  • Android自定义view,怎么不会销毁之前的然后重绘啊?下面贴上代码,请大神帮忙看下 ![图片说明](https://img-ask.csdn.net/upload/201707/24/1500888556_916529.gif) 源码如下: ``` import android....
  • 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了...现在前半部分的承诺已经如约兑现了,那么今天我就要来兑现后面部分的承诺,讲一讲自定义View的实现方法,同时这也是带
  • Android自定义View

    千次阅读 2012-10-09 16:21:33
    Android自定义View需要继承View,重写构造函数、onDraw,(onMeasure)等函数。 如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml。在其中定义你的属性。 在使用到自定义View的xml布局文件中需要...
  • Android自定义view刷新方法

    千次阅读 2019-08-26 22:17:34
    Android view的刷新有三个方式: //只会触发执行onDraw方法,只会改变绘制里面的内容,条目的绘制 invalidate(); //只会触发执行onDraw方法,但是可以在子线程中刷新 postInvalidate(); //view的布局参数改变...
  • Android 自定义View步骤

    2017-09-01 11:02:06
    Android 实现一个简单的自定义View Android 自定义View步骤 Android 自定义View之Canvas相关方法说明 Android 自定义View实例(验证码) Android 自定义View实例(进度圆环) Android 源码分析(TextView) Android ...
  • Android 自定义View手写签名

    千次阅读 2019-07-02 19:15:37
    Android 自定义View:手写签名 最近项目中有个新的需求,就是要实现用户手写签名,然后展示再上传到服务器。看到效果图后,先是面对百度编程搜了一下,很多实现方法,主要就是自定义View实现的,为了记录其中的坑,...
  • android 自定义View -传值

    千次阅读 2019-06-23 19:18:38
    文章目录前言实现 前言 ... 大概需要如下: 这是设置密码的view,还是检查密码的view 设定密码的view需要获取最后的password ...学习资料:《Android自定义控件开发入门与实践》 - 第12章 我怎么在fragment中获取和设...

空空如也

1 2 3 4 5 ... 20
收藏数 28,966
精华内容 11,586
关键字:

android自定义view