【Android】自定义控件让TextView的drawableLeft与文本一起居中显示

前言 TextView的drawableLeft、drawableRight和drawableTop是一个常用、好用的属性,可以在文本的上下左右放置一个图片,而不使用更加复杂布局就能达到,我也常常喜欢用RadioButton的这几个属性实现很多效果,但是苦于不支持让drawbleLeft与文本一起居中,设置gravity为center也无济于事,终于有空研究了一下,这里与大家一起分享。 声明 欢迎转载,请注明出处! 博客园:http://www.cnblogs.com/ 农民伯伯: http://www.cnblogs.com/over140/ 正文 一、效果图 二、实现代码 自定义控件 ![复制代码](http://common.cnblogs.com/images/copycode.gif) /** * drawableLeft与文本一起居中显示 * * @author 农民伯伯 * @see http://www.cnblogs.com/over140/p/3464348.html * */ public class DrawableCenterTextView extends TextView { public DrawableCenterTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public DrawableCenterTextView(Context context, AttributeSet attrs) { super(context, attrs); } public DrawableCenterTextView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { Drawable[] drawables = getCompoundDrawables(); if (drawables != null) { Drawable drawableLeft = drawables[0]; if (drawableLeft != null) { float textWidth = getPaint().measureText(getText().toString()); int drawablePadding = getCompoundDrawablePadding(); int drawableWidth = 0; drawableWidth = drawableLeft.getIntrinsicWidth(); float bodyWidth = textWidth + drawableWidth + drawablePadding; } } super.onDraw(canvas); } } ...

2016年7月19日 · 1 分钟 · 天边的星星

Android自定义控件系列二:自定义开关按钮(一)

这一次我们将会实现一个完整纯粹的自定义控件,而不是像之前的组合控件一样,拿系统的控件来实现;计划分为三部分:自定义控件的基本部分,自定义控件的触摸事件的处理和自定义控件的自定义属性; 下面就开始第一部分的编写,本次以一个定义的开关按钮为例,下面就开始吧: 先看看效果,一个点击开关按钮,实现点击切换开关状态: 为了能够讲解清晰,还是来一些基本的介绍。 首先需要明确的就是自定义控件还是继承自View这个类,Google在View这个类里面提供了相当多的方法供我们使用,使用这些方法我们可以实现相当多的效果和功能,在这里需要用到几个主要的方法; 自定义控件的步骤、用到的主要方法: 1、首先需要定义一个类,继承自View;对于继承View的类,会需要实现至少一个构造方法;实际上这里一共有三个构造方法: **public View (Context context)**是在java代码创建视图的时候被调用(使用new的方式),如果是从xml填充的视图,就不会调用这个 **public View (Context context, AttributeSet attrs)**这个是在xml创建但是没有指定style的时候被调用 **public View (Context context, AttributeSet attrs, int defStyle)**这个是在第二个基础上添加style的时候被调用的 所以对于这里来说,如果不使用style, 我们重点关注第二个构造方法即可 2、对于任何一个控件来说,它需要显示在我们的界面上,那么肯定需要定义它的大小; 在这里Google提供了一个方法:**protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);我们去看这个方法的内部,实际上是调用了protected final void setMeasuredDimension(int measuredWidth, int measuredHeight);**这个方法,其中第一个参数是view的宽,第二个参数是view的高,这样我们就可以设置view的宽高了,但是要注意,这样设置的单位都是像素 3、对于一个需要显示的控件来说,我们往往还需要确定它的位置: 这就要求重写onLayout方法;但是实际上这个方法在自定义view的时候使用的不多,原因是因为对于位置来说,控件只有建议权而没有决定权,决定权一般在父控件那里。 4、对于一个控件,需要显示,我们当然需要将它绘制出来,这里就需要重写onDraw方法,来将这个控件绘制出来 5、当控件状态改变的时候,我们很可能需要刷新view的显示状态,这时候就需要调用invalidate()方法,这个方法实际上会重新调用onDraw方法来重绘控件 6、在定义控件的过程中,如果需要对view设置点击事件,可以直接使用setOnClickListener方法,而不需要写view.setOnClickListener; **7、在布局文件中将这个自定义控件定义出来,注意名字要使用全类名;**而且,由于是继承自view控件,所以在xml文件中如果是view本身的属性都可以直接使用,比如:android:layout_width等等 这里比较关键的地方就在于这个onDraw方法,我们一起来看一下: **[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/40736027#) [copy](http://blog.csdn.net/cyp331203/article/details/40736027#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/505895)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/505895/fork)</span></div> </div> - <span class="comment">/**</span> - <span class="comment"> * 画view的方法,绘制当前view的内容</span> - <span class="comment"> */</span> - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onDraw(Canvas canvas) { - <span class="comment">// super.onDraw(canvas);</span> - - - Paint paint = <span class="keyword">new</span> Paint(); - <span class="comment">// 打开抗锯齿</span> - paint.setAntiAlias(<span class="keyword">true</span>); - - - <span class="comment">// 画背景</span> - canvas.drawBitmap(backgroundBitmap, <span class="number"></span>, <span class="number"></span>, paint); - <span class="comment">// 画滑块</span> - canvas.drawBitmap(slideButton, slideBtn_left, <span class="number"></span>, paint); - } </div> &nbsp; **onDraw**方法传入的参数是一个**Canvas**画布对象,这个实际上跟Java中的差不太多,我们要在画布上画画也需要一个画笔,我们这里也将其初始化出来**Paint paint = new Paint()**,同时设置了一个抗锯齿效果**paint.setAntiAlias(true)**,然后调用**drawBitmap**的方法,先后绘制了开关的背景和开关的滑块,分别入下图: ![](http://img.blog.csdn.net/20141103103403203?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3lwMzMxMjAz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) ![](http://img.blog.csdn.net/20141103103342450?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3lwMzMxMjAz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 这里要注意的一点就是,**drawBitmap(Bitmap bitmap, float left, float top, Paint paint)**方法中间的两个float类型的参数,分别代表绘制图形的左上角的x和y的坐标(原点设置在左上角),所以这里如果我们个绘制坐标都传入0,0,那么开关会处在一个关的状态,这里,我们对于滑块使用了一个变量**slideBtn_left**来设置其位置,**那么对于关闭状态,slideBtn_left的值就应该为0,对于开启状态,slideBtn_left的值就应该是backgroundBitmap(背景)的宽度减去slideButton(滑块)的宽度**; 那么这样一来,机制就比较清楚了,我们只需要在控件上设置一个点击事件,同时设置一个boolean变量代表开关的状态,当点击的时候,切换这个boolean类型的变量为true或者false,同时变化**slideButton**的值为****或者**backgroundBitmap.getWidth()-slideButton.getWidth()**,然后再调用**invalidate()**方法刷新控件,就可以实现基本的开关功能了 下面来看具体的代码,注解比较详细: 自定义控件的类MyToggleButton.java,继承自View: <div class="dp-highlighter bg_java"> <div class="bar"> <div class="tools"> **[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/40736027#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/40736027#)</span></span> <div> <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/505895)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/505895/fork)</span></div> </div> - <span class="keyword">package</span> com.example.togglebutton.ui; - - <span class="keyword">import</span> com.example.togglebutton.R; - - <span class="keyword">import</span> android.content.Context; - <span class="keyword">import</span> android.graphics.Bitmap; - <span class="keyword">import</span> android.graphics.BitmapFactory; - <span class="keyword">import</span> android.graphics.Canvas; - <span class="keyword">import</span> android.graphics.Paint; - <span class="keyword">import</span> android.util.AttributeSet; - <span class="keyword">import</span> android.view.View; - - <span class="comment">/*</span> - <span class="comment"> * 自定义view的几个步骤:</span> - <span class="comment"> * 1、首先需要写一个类来继承自View</span> - <span class="comment"> * 2、需要得到view的对象,那么需要重写构造方法,其中一参的构造方法用于new,二参的构造方法用于xml布局文件使用,三参的构造方法可以传入一个样式</span> - <span class="comment"> * 3、需要设置view的大小,那么需要重写onMeasure方法</span> - <span class="comment"> * 4、需要设置view的位置,那么需要重写onLayout方法,但是这个方法在自定义view的时候用的不多,原因主要在于view的位置主要是由父控件来决定</span> - <span class="comment"> * 5、需要绘制出所需要显示的view,那么需要重写onDraw方法</span> - <span class="comment"> * 6、当控件状态改变的时候,需要重绘view,那么调用invalidate();方法,这个方法实际上会重新调用onDraw方法</span> - <span class="comment"> * 7、在这其中,如果需要对view设置点击事件,可以直接调用setOnClickListener方法</span> - <span class="comment"> */</span> - - <span class="keyword">public</span> <span class="keyword">class</span> MyToggleButton <span class="keyword">extends</span> View { - - <span class="comment">/**</span> - <span class="comment"> * 开关按钮的背景</span> - <span class="comment"> */</span> - <span class="keyword">private</span> Bitmap backgroundBitmap; - <span class="comment">/**</span> - <span class="comment"> * 开关按钮的滑动部分</span> - <span class="comment"> */</span> - <span class="keyword">private</span> Bitmap slideButton; - <span class="comment">/**</span> - <span class="comment"> * 滑动按钮的左边界</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> slideBtn_left; - <span class="comment">/**</span> - <span class="comment"> * 当前开关的状态</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">boolean</span> currentState = <span class="keyword">false</span>; - - <span class="comment">/**</span> - <span class="comment"> * 在代码里面创建对象的时候,使用此构造方法</span> - <span class="comment"> * </span> - <span class="comment"> * @param context</span> - <span class="comment"> */</span> - <span class="keyword">public</span> MyToggleButton(Context context) { - <span class="keyword">super</span>(context); - } - - <span class="comment">/**</span> - <span class="comment"> * 在布局文件中声明的view,创建时由系统自动调用</span> - <span class="comment"> * </span> - <span class="comment"> * @param context</span> - <span class="comment"> * @param attrs</span> - <span class="comment"> */</span> - <span class="keyword">public</span> MyToggleButton(Context context, AttributeSet attrs) { - <span class="keyword">super</span>(context, attrs); - initView(); - } - - <span class="comment">/**</span> - <span class="comment"> * 测量尺寸时的回调方法</span> - <span class="comment"> */</span> - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) { - <span class="comment">// super.onMeasure(widthMeasureSpec, heightMeasureSpec);</span> - <span class="comment">// 设置当前view的大小 width:view的宽,单位都是像素值 heigth:view的高,单位都是像素值</span> - setMeasuredDimension(backgroundBitmap.getWidth(), - backgroundBitmap.getHeight()); - } - - <span class="comment">// 这个方法对于自定义view的时候帮助不大,因为view的位置一般由父组件来决定的</span> - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onLayout(<span class="keyword">boolean</span> changed, <span class="keyword">int</span> left, <span class="keyword">int</span> top, <span class="keyword">int</span> right, - <span class="keyword">int</span> bottom) { - <span class="keyword">super</span>.onLayout(changed, left, top, right, bottom); - } - - <span class="comment">/**</span> - <span class="comment"> * 画view的方法,绘制当前view的内容</span> - <span class="comment"> */</span> - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onDraw(Canvas canvas) { - <span class="comment">// super.onDraw(canvas);</span> - - Paint paint = <span class="keyword">new</span> Paint(); - <span class="comment">// 打开抗锯齿</span> - paint.setAntiAlias(<span class="keyword">true</span>); - - <span class="comment">// 画背景</span> - canvas.drawBitmap(backgroundBitmap, <span class="number"></span>, <span class="number"></span>, paint); - <span class="comment">// 画滑块</span> - canvas.drawBitmap(slideButton, slideBtn_left, <span class="number"></span>, paint); - } - - <span class="comment">/**</span> - <span class="comment"> * 初始化view</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> initView() { - backgroundBitmap = BitmapFactory.decodeResource(getResources(), - R.drawable.switch_background); - slideButton = BitmapFactory.decodeResource(getResources(), - R.drawable.slide_button); - - <span class="comment">/*</span> - <span class="comment"> * 点击事件</span> - <span class="comment"> */</span> - setOnClickListener(<span class="keyword">new</span> OnClickListener() { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onClick(View v) { - - currentState = !currentState; - flushState(); - flushView(); - } - }); - } - - <span class="comment">/**</span> - <span class="comment"> * 刷新视图</span> - <span class="comment"> */</span> - <span class="keyword">protected</span> <span class="keyword">void</span> flushView() { - <span class="comment">// 刷新当前view会导致ondraw方法的执行</span> - invalidate(); - } - - <span class="comment">/**</span> - <span class="comment"> * 刷新当前的状态</span> - <span class="comment"> */</span> - <span class="keyword">protected</span> <span class="keyword">void</span> flushState() { - <span class="keyword">if</span> (currentState) { - slideBtn_left = backgroundBitmap.getWidth() - &#8211; slideButton.getWidth(); - } <span class="keyword">else</span> { - slideBtn_left = <span class="number"></span>; - } - } - - } </div> &nbsp; 在布局文件中将其定义出来: <div class="dp-highlighter bg_html"> <div class="bar"> <div class="tools"> **[html]** [view plain](http://blog.csdn.net/cyp331203/article/details/40736027#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/40736027#)</span></span> <div> <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/505895)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/505895/fork)</span></div> </div> - <span class="tag"><</span><span class="tag-name">RelativeLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">xmlns:tools</span>=<span class="attribute-value">&#8220;http://schemas.android.com/tools&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">tools:context</span>=<span class="attribute-value">&#8220;<span class="katex math inline">{relativePackage}.</span>{activityClass}&#8221;</span> <span class="tag">></span> - - - <span class="tag"><</span><span class="tag-name">com.example.togglebutton.ui.MyToggleButton</span> - <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/my_toggle_btn&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_centerInParent</span>=<span class="attribute-value">&#8220;true&#8221;</span> <span class="tag">/></span> - - - <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span> </div> &nbsp; 在这里由于没有写任何点击触发业务的逻辑,只是一个单纯的控件,所以在MainActivity里面没有加入多的代码: <div class="dp-highlighter bg_java"> <div class="bar"> <div class="tools"> **[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/40736027#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/40736027#)</span></span> <div> <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/505895)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/505895/fork)</span></div> </div> - <span class="keyword">package</span> com.example.togglebutton; - - <span class="keyword">import</span> android.app.Activity; - <span class="keyword">import</span> android.os.Bundle; - - <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity { - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) { - <span class="keyword">super</span>.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - } - } </div> 至此一个自定义的开关按钮就完成了,后面两篇将会介绍如何在上面实现 **[点击拖动开关的效果](http://blog.csdn.net/cyp331203/article/details/40779335)** 和如何实现自定义属性,谢谢支持!

2016年4月6日 · 5 分钟 · 天边的星星

Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)

转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45027641 自定义view/viewgroup要重写的几个方法:onMeasure(),onLayout(),onDraw()。(不熟悉的话可以查看专栏的前几篇文章:Android自定义控件系列二:自定义开关按钮(一))。 今天的任务就是详细研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。 如果只是说要重写什么方法有什么用的话,还是不太清楚。先去源码中看看为什么要重写onMeasure()方法,这个方法是在哪里调用的: 一、源码中的measure/onMeasure方法: **[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#) [copy](http://blog.csdn.net/cyp331203/article/details/45027641#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/642862/fork)</span></div> </div> - <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) { - setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), - getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); - } </div> &nbsp; 实际上是在View这个类中的public final void measure(int widthMeasureSpec, int heightMeasureSpec)方法中被调用的: <div class="dp-highlighter bg_java"> <div class="bar"> <div class="tools"> **[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)</span></span> <div> <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/642862/fork)</span></div> </div> - <span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> measure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) { - &#8230; - - onMeasure(widthMeasureSpec, heightMeasureSpec); - &#8230; - - } </div> &nbsp; ## <a name="t1"></a>1、measure() 可以看到,measure()这个方法是一个由final来修饰的方法,意味着不能够被子类重写.measure()方法的作用是:测量出一个View的实际大小,而实际性的测量工作,Android系统却并没有帮我们完成,因为这个工作交给了onMeasure()来作,所以我们需要在自定义View的时候按照自己的需求,重写onMeasure方法.而子控件又分为view和viewGroup两种情况,那么测量的流程是怎样的呢,看一下下面这个图你就明白了: ![](http://img.blog.csdn.net/20150413165429783) ## <a name="t2"></a> ## <a name="t3"></a>2、onMeasure onMeasure(int widthMeasureSpec, int heightMeasureSpec)中,两个参数的作用: widthMeasureSpec和heightMeasureSpec这两个int类型的参数,看名字应该知道是跟宽和高有关系,但它们其实不是宽和高,而是由宽、高和各自方向上对应的模式来合成的一个值:其中,在int类型的32位二进制位中,31-30这两位表示模式,0~29这三十位表示宽和高的实际值.其中模式一共有三种,被定义在Android中的View类的一个内部类中:View.MeasureSpec: ①UNSPECIFIED:表示默认值,父控件没有给子view任何限制。&#8212;&#8212;二进制表示:00 ②EXACTLY:表示父控件给子view一个具体的值,子view要设置成这些值的大小。&#8212;&#8212;二进制表示:01 ③AT_MOST:表示父控件个子view一个最大的特定值,而子view不能超过这个值的大小。&#8212;&#8212;二进制表示:10 # <a name="t4"></a>二、MeasureSpec MeasureSpe描述了父View对子View大小的期望.里面包含了测量模式和大小.我们可以通过以下方式从MeasureSpec中提取模式和大小,该方法内部是采用位移计算. int specMode = MeasureSpec.getMode(measureSpec);//得到模式 int specSize = MeasureSpec.getSize(measureSpec);//得到大小 也可以通过MeasureSpec的静态方法把大小和模式合成,该方法内部只是简单的相加. MeasureSpec.makeMeasureSpec(specSize,specMode); 每个View都包含一个ViewGroup.LayoutParams类或者其派生类,LayoutParams中包含了View和它的父View之间的关系,而View大小正是View和它的父View共同决定的。 我们平常使用类似于RelativeLayout和LinearLayout的时候,在其内部添加view的时候,不管是布局文件中加入还是在代码中使用addView方法添加,实际上都会调用这个onMeasure方法,而measure和onMeasure中的两个参数,是由各级父控件往子控件/子view进行一层层传递的。我们可以在xml中定义Layout的宽和高的具体的值或宽高的填充方式:matchparent/wrapcontent,也可以在代码中使用LayoutParams设置,而实际上这里设置的值就会对应到上面的measure和onMeasure方法中的两个参数的模式,对应关系如下: 具体的值(如width=200dp)和matchparent/fillparent,对应模式中的MeasureSpec.EXACTLY 包裹内容(width=wrapcontent)则对应模式中的MeasureSpec.AT_MOST 系统调用measure方法,从父控件到子控件的heightMeasureSpec的传递是有一套对应的判断规则的,列表如下: ![](http://img.blog.csdn.net/20150413170503566) 一个view的宽高尺寸,只有在测量之后才能得到,也就是measure方法被调用之后。大家都应该使用过View.getWidth()和View.getHeight()方法,这两个方法可以返回view的宽和高,但是它们也不是在一开始就可以得到的,比如oncreate方法中,因为这时候measure方法还没有被执行,测量还没有完成,我们可以来作一个简单的实验:自定义一个MyView,继承View类,然后在OnCreate方法中,将其new出来,通过addview方法,添加到现在的布局中。然后调用MyView对象的getWidth()和getHeight()方法,会发现得到的都是0。 onMeasure通过父View传递过来的大小和模式,以及自身的背景图片的大小得出自身最终的大小,然后通过setMeasuredDimension()方法设置给mMeasuredWidth和mMeasuredHeight. 普通View的onMeasure逻辑大同小异,基本都是测量自身内容和背景,然后根据父View传递过来的MeasureSpec进行最终的大小判定,例如TextView会根据文字的长度,文字的大小,文字行高,文字的行宽,显示方式,背景图片,以及父View传递过来的模式和大小最终确定自身的大小. # <a name="t5"></a>三、ViewGroup的onMeasure ViewGroup是个抽象类,本身没有实现onMeasure,但是他的子类都有各自的实现,通常他们都是通过measureChildWithMargins函数或者其他类似于measureChild的函数来遍历测量子View,被GONE的子View将不参与测量,当所有的子View都测量完毕后,才根据父View传递过来的模式和大小来最终决定自身的大小. 在测量子View时,会先获取子View的LayoutParams,从中取出宽高,如果是大于0,将会以精确的模式加上其值组合成MeasureSpec传递子View,如果是小于0,将会把自身的大小或者剩余的大小传递给子View,其模式判定在前面表中有对应关系. ViewGroup一般都在测量完所有子View后才会调用setMeasuredDimension()设置自身大小,如第一张图所示. 可能看到现在,还是没搞清楚Android系统通过measure和onmeasure一层层传递参数的具体方法。在研究这个问题之前,先来看一下最简单的helloworld的UI层级关系图: 为了方便起见,这里我们使用requestWindowFeature(Window.FEATURE_NO_TITLE);去除标题栏的影响,只看层级关系。 <div class="dp-highlighter bg_html"> <div class="bar"> <div class="tools"> **[html]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)</span></span> <div> <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/642862/fork)</span></div> </div> - <span class="tag"><</span><span class="tag-name">RelativeLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span> - <span class="attribute">xmlns:tools</span>=<span class="attribute-value">&#8220;http://schemas.android.com/tools&#8221;</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span> - <span class="attribute">tools:context</span>=<span class="attribute-value">&#8220;<span class="katex math inline">{relativePackage}.</span>{activityClass}&#8221;</span> <span class="tag">></span> - - <span class="tag"><</span><span class="tag-name">TextView</span> - <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;wrap_content&#8221;</span> - <span class="attribute">android:text</span>=<span class="attribute-value">&#8220;@string/hello_world&#8221;</span> <span class="tag">/></span> - - <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span> </div> &nbsp; <div class="dp-highlighter bg_java"> <div class="bar"> <div class="tools"> **[java]** [view plain](http://blog.csdn.net/cyp331203/article/details/45027641#)<span class="tracking-ad" data-mod="popu_168"><span class="tracking-ad" data-mod="popu_168"> [copy](http://blog.csdn.net/cyp331203/article/details/45027641#)</span></span> <div> <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4"> </embed> </div> <span class="tracking-ad" data-mod="popu_167">[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/642862)</span><span class="tracking-ad" data-mod="popu_170">[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/642862/fork)</span></div> </div> - <span class="keyword">package</span> com.example.hello; - - <span class="keyword">import</span> android.app.Activity; - <span class="keyword">import</span> android.os.Bundle; - <span class="keyword">import</span> android.view.Window; - - <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity { - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) { - <span class="keyword">super</span>.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.activity_main); - } - } </div> UI层级关系图: ![](http://img.blog.csdn.net/20150413172415210) 可以发现最简单的helloworld的层级关系图是这样的,最开始是一个PhoneWindow的内部类DecorView,这个DecorView实际上是系统最开始加载的最底层的一个viewGroup,它是FrameLayout的子类,然后加载了一个LinearLayout,然后在这个LinearLayout上加载了一个id为content的FrameLayout和一个ViewStub,这个实际上是原本为ActionBar的位置,由于我们使用了requestWindowFeature(Window.FEATURE_NO_TITLE),于是变成了空的ViewStub;然后在id为content的FrameLayout才加载了我们的布局XML文件中写的RelativeLayout和TextView。 那么measure方法在系统中传递尺寸和模式,必定是从DecorView这一层开始的,我们假定手机屏幕是320*480,那么DecorView最开始是从硬件的配置文件中读取手机的尺寸,然后设置measure的参数大小为320*480,而模式是EXCACTLY,传递关系可以由下图示意: ![](http://img.blog.csdn.net/20150413174301575) <div> </div> &nbsp; 好了,原理将到这里,下一篇将看到利用onMeasure来测量一个自定义一个ImageView,使其能够自动填满屏幕的宽度,且能通过measure测量高度,自适应的调整高度,永远不出现拉伸/压缩变形的情况,敬请关注,谢谢。 [Android自定义控件系列八:详解onMeasure()(二)&#8211;利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题](http://blog.csdn.net/cyp331203/article/details/45038329) **转载请注明出处:[http://blog.csdn.net/cyp331203/article/details/45027641](http://blog.csdn.net/cyp331203/article/details/45027641)**

2016年4月6日 · 2 分钟 · 天边的星星

Android 自定义控件——图片剪裁

该控件由另一篇文章:Android 图片拖拽、放大缩小的自定义控件 扩展而来 http://www.linuxidc.com/Linux/2014-12/110763.htm 如图: 思路:在一个自定义View上绘制一张图片(参照前面提到的另一篇文章),在该自定义View上绘制一个自定义的FloatDrawable,也就是图中的浮层。绘制图片和FloatDrawable的交集的补集部分灰色阴影(这个其实很简单,就一句话)。在自定义View的touch中去处理具体的拖动事件和FloatDrawable的变换。图片的绘制和FloatDrawable的绘制以及变换最终其实就是在操作各自的Rect而已,Rect就是一个有矩形,有四个坐标,图片和FloatDrawable就是按照坐标去绘制的。 CropImageView.java 该类继承View 功能:在onDraw方法中画图片、浮层,处理touch事件,最后根据坐标对图片进行剪裁。 public class CropImageView extends View { // 在touch重要用到的点, private float mX_1 = 0; private float mY_1 = 0; // 触摸事件判断 private final int STATUS_SINGLE = 1; private final int STATUS_MULTI_START = 2; private final int STATUS_MULTI_TOUCHING = 3; // 当前状态 private int mStatus = STATUS_SINGLE; // 默认裁剪的宽高 private int cropWidth; private int cropHeight; // 浮层Drawable的四个点 private final int EDGE_LT = 1; private final int EDGE_RT = 2; private final int EDGE_LB = 3; private final int EDGE_RB = 4; private final int EDGE_MOVE_IN = 5; private final int EDGE_MOVE_OUT = 6; private final int EDGE_NONE = 7; ...

2016年2月19日 · 7 分钟 · 天边的星星

Android自定义控件

开发自定义控件的步骤: 1、了解View的工作原理 2、 编写继承自View的子类 3、 为自定义View类增加属性 4、 绘制控件 5、 响应用户消息 6 、自定义回调函数 一、View结构原理 Android系统的视图结构的设计也采用了组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类。 View定义了绘图的基本操作 基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。具体操作如下: 1、measure操作 measure操作主要用于计算视图的大小,即视图的宽度和长度。在view中定义为final类型,要求子类不能修改。measure()函数中又会调用下面的函数: (1)onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。 2、layout操作 layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作: (1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来; (2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的; 3、draw操作 draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作: (1)绘制背景; (2)如果要视图显示渐变框,这里会做一些准备工作; (3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法; (4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法; (5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框; (6)绘制滚动条; 从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法。 二、View类的构造方法 创建自定义控件的3种主要实现方式: 1)继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。 2)通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。 注意此时不用onDraw方法,在构造广告中通过inflater加载自定义控件的布局文件,再addView(view),自定义控件的图形界面就加载进来了。 3)通过继承view类来实现自定义控件,使用GDI绘制出组件界面,一般无法通过上述两种方式来实现时用该方式。 三、自定义View增加属性的两种方法: 1)在View类中定义。通过构造函数中引入的AttributeSet 去查找XML布局的属性名称,然后找到它对应引用的资源ID去找值。 案例:实现一个带文字的图片(图片、文字是onDraw方法重绘实现) ![](http://common.cnblogs.com/images/copycode.gif) publicclassMyViewextendsView {privateString mtext;privateintmsrc;publicMyView(Context context) {super(context); }publicMyView(Context context, AttributeSet attrs) {super(context, attrs);intresourceId = 0;inttextId = attrs.getAttributeResourceValue(null, “Text”,0);intsrcId = attrs.getAttributeResourceValue(null, “Src”, 0); mtext=context.getResources().getText(textId).toString(); msrc=srcId; } @OverrideprotectedvoidonDraw(Canvas canvas) { ...

2015年7月10日 · 1 分钟 · 天边的星星

Android自定义控件(状态提示图表)

1 背景 前面分析那么多系统源码了,也该暂停下来休息一下,趁昨晚闲着看见一个有意思的需求就操练一下分析源码后的实例演练—-自定义控件。 这个实例很适合新手入门自定义控件。先看下效果图: 横屏模式如下: 竖屏模式如下: 看见没有,这个控件完全自定义的,连文字等都是自定义的,没有任何图片等资源,就仅仅是一个小的java文件,这个界面只有一个控件。如下咱们看下实现代码。 !!!!!!! 下载Demo工程源码点击我 【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】 2 实例代码 如下就是整个工程的源码了。 自定义上面展示的控件AreaChartsView源码: `&lt;span class="hljs-javadoc">/** * Author : yanbo * Date : 2015-06-03 * Time : 09:22 * Description : 自定义区域描述图表View */&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-class">&lt;span class="hljs-keyword">class&lt;/span> &lt;span class="hljs-title">AreaChartsView&lt;/span> &lt;span class="hljs-keyword">extends&lt;/span> &lt;span class="hljs-title">View&lt;/span> {&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> Paint mPaint; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mZeroPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mMaxYPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[] mMaxXPos = &lt;span class="hljs-keyword">new&lt;/span> &lt;span class="hljs-keyword">int&lt;/span>[&lt;span class="hljs-number">2&lt;/span>]; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mWidth, mHight; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mRealWidth, mRealHight; &lt;span class="hljs-keyword">private&lt;/span> String mTitleY, mTitleX; &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mXLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mYLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;String&gt; mGridLevelText = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mGridColorLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> ArrayList&lt;Integer&gt; mGridTxtColorLevel = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;&gt;(); &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mGridLevel = mXLevel.size() - &lt;span class="hljs-number">1&lt;/span>; &lt;span class="hljs-comment">//title字符大小&lt;/span> &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mXYTitleTextSize = &lt;span class="hljs-number">40&lt;/span>; &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> mMeasureXpos, mMeasureYpos; &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-title">AreaChartsView&lt;/span>(Context context, AttributeSet attrs) { &lt;span class="hljs-keyword">super&lt;/span>(context, attrs); mPaint = &lt;span class="hljs-keyword">new&lt;/span> Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setAntiAlias(&lt;span class="hljs-keyword">true&lt;/span>); mPaint.setFilterBitmap(&lt;span class="hljs-keyword">true&lt;/span>); } &lt;span class="hljs-annotation">@Override&lt;/span> &lt;span class="hljs-keyword">protected&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onLayout&lt;/span>(&lt;span class="hljs-keyword">boolean&lt;/span> changed, &lt;span class="hljs-keyword">int&lt;/span> left, &lt;span class="hljs-keyword">int&lt;/span> top, &lt;span class="hljs-keyword">int&lt;/span> right, &lt;span class="hljs-keyword">int&lt;/span> bottom) { &lt;span class="hljs-keyword">super&lt;/span>.onLayout(changed, left, top, right, bottom); mWidth = getWidth(); mHight = getHeight(); } &lt;span class="hljs-annotation">@Override&lt;/span> &lt;span class="hljs-keyword">protected&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onDraw&lt;/span>(Canvas canvas) { &lt;span class="hljs-keyword">super&lt;/span>.onDraw(canvas); initPosition(); drawXYTitle(canvas); drawXYLine(canvas); drawContent(canvas); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initPosition&lt;/span>() { &lt;span class="hljs-comment">//初始化坐标图的xy交点原点坐标&lt;/span> mZeroPos[&lt;span class="hljs-number">0&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; mZeroPos[&lt;span class="hljs-number">1&lt;/span>] = mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>; &lt;span class="hljs-comment">//初始化坐标图的X轴最大值坐标&lt;/span> mMaxXPos[&lt;span class="hljs-number">0&lt;/span>] = mWidth; mMaxXPos[&lt;span class="hljs-number">1&lt;/span>] = mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>; &lt;span class="hljs-comment">//初始化坐标图的Y轴最大值坐标&lt;/span> mMaxYPos[&lt;span class="hljs-number">0&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] = mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>; } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawXYTitle&lt;/span>(Canvas canvas) { mPaint.setColor(Color.parseColor(&lt;span class="hljs-string">"#1FB0E7"&lt;/span>)); mPaint.setTextSize(mXYTitleTextSize); mPaint.setTextAlign(Paint.Align.LEFT); &lt;span class="hljs-comment">//画Y轴顶的title&lt;/span> canvas.drawText(mTitleY, mMaxYPos[&lt;span class="hljs-number">0&lt;/span>] - mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>, mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] - mXYTitleTextSize, mPaint); mPaint.setTextAlign(Paint.Align.RIGHT); &lt;span class="hljs-comment">//画X轴顶的title&lt;/span> canvas.drawText(mTitleX, mMaxXPos[&lt;span class="hljs-number">0&lt;/span>], mMaxXPos[&lt;span class="hljs-number">1&lt;/span>] + mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>, mPaint); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawXYLine&lt;/span>(Canvas canvas) { mPaint.setColor(Color.DKGRAY); mPaint.setTextAlign(Paint.Align.RIGHT); &lt;span class="hljs-comment">//画XY轴&lt;/span> canvas.drawLine(mMaxYPos[&lt;span class="hljs-number">0&lt;/span>], mMaxYPos[&lt;span class="hljs-number">1&lt;/span>], mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); canvas.drawLine(mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>], mMaxXPos[&lt;span class="hljs-number">0&lt;/span>], mMaxXPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawContent&lt;/span>(Canvas canvas) { mGridLevel = mXLevel.size() - &lt;span class="hljs-number">1&lt;/span>; &lt;span class="hljs-comment">//计算出偏移title等显示尺标后的真实XY轴长度,便于接下来等分&lt;/span> mRealWidth = (mWidth - mXYTitleTextSize * &lt;span class="hljs-number">2&lt;/span>); mRealHight = (mHight - mXYTitleTextSize * &lt;span class="hljs-number">4&lt;/span>); &lt;span class="hljs-comment">//算出等分间距&lt;/span> &lt;span class="hljs-keyword">int&lt;/span> offsetX = mRealWidth/(mGridLevel); &lt;span class="hljs-keyword">int&lt;/span> offsetY = mRealHight/(mGridLevel+&lt;span class="hljs-number">1&lt;/span>); &lt;span class="hljs-comment">//循环绘制content&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel+&lt;span class="hljs-number">1&lt;/span>; index++) { mPaint.setColor(Color.DKGRAY); mPaint.setTextAlign(Paint.Align.RIGHT); mPaint.setTextSize(mXYTitleTextSize-&lt;span class="hljs-number">5&lt;/span>); &lt;span class="hljs-comment">//绘制X轴的那些坐标区间点,包含0点坐标&lt;/span> canvas.drawText(String.valueOf(mXLevel.get(index)), mZeroPos[&lt;span class="hljs-number">0&lt;/span>]+(index*offsetX), mZeroPos[&lt;span class="hljs-number">1&lt;/span>] + mXYTitleTextSize, mPaint); &lt;span class="hljs-keyword">if&lt;/span> (index != &lt;span class="hljs-number">0&lt;/span>) { &lt;span class="hljs-comment">//绘制Y轴坐标区间点,不包含0点坐标,X轴已经画过了&lt;/span> canvas.drawText(String.valueOf(mYLevel.get(index)), mZeroPos[&lt;span class="hljs-number">0&lt;/span>], mZeroPos[&lt;span class="hljs-number">1&lt;/span>]-(index*offsetY), mPaint); } &lt;span class="hljs-keyword">if&lt;/span> (index == mGridLevel) { &lt;span class="hljs-comment">//坐标区间 = 真实区间 + 1&lt;/span> &lt;span class="hljs-keyword">break&lt;/span>; } mPaint.setColor(mGridColorLevel.get(mGridLevel - &lt;span class="hljs-number">1&lt;/span> - index)); mPaint.setStyle(Paint.Style.FILL); &lt;span class="hljs-comment">//绘制区间叠加图谱方块,从远到0坐标,因为小的图会覆盖大的图&lt;/span> canvas.drawRect(mMaxYPos[&lt;span class="hljs-number">0&lt;/span>], mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] + index*offsetY, mMaxXPos[&lt;span class="hljs-number">0&lt;/span>]-index*offsetX, mMaxXPos[&lt;span class="hljs-number">1&lt;/span>], mPaint); mPaint.setColor(mGridTxtColorLevel.get(index)); mPaint.setTextAlign(Paint.Align.RIGHT); mPaint.setTextSize(mXYTitleTextSize); &lt;span class="hljs-comment">//绘制每个方块状态区间的提示文字&lt;/span> canvas.drawText(mGridLevelText.get(index), mMaxXPos[&lt;span class="hljs-number">0&lt;/span>] - index * offsetX - mXYTitleTextSize, mMaxYPos[&lt;span class="hljs-number">1&lt;/span>] + index * offsetY + mXYTitleTextSize, mPaint); } &lt;span class="hljs-comment">//绘制当前坐标&lt;/span> drawNotice(canvas, offsetX, offsetY); } &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">drawNotice&lt;/span>(Canvas canvas, &lt;span class="hljs-keyword">int&lt;/span> offsetX, &lt;span class="hljs-keyword">int&lt;/span> offsetY) { &lt;span class="hljs-keyword">int&lt;/span> realPosX = &lt;span class="hljs-number">0&lt;/span>; &lt;span class="hljs-keyword">int&lt;/span> realPosY = &lt;span class="hljs-number">0&lt;/span>; &lt;span class="hljs-comment">//计算传入的x值与真实屏幕坐标的像素值的百分比差值转换&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel; index++) { &lt;span class="hljs-keyword">if&lt;/span> (mMeasureXpos &gt;= mXLevel.get(index) && mMeasureXpos &lt; mXLevel.get(index+&lt;span class="hljs-number">1&lt;/span>)) { &lt;span class="hljs-keyword">int&lt;/span> subValue = mMeasureXpos - mXLevel.get(index); &lt;span class="hljs-keyword">int&lt;/span> offset = mXLevel.get(index+&lt;span class="hljs-number">1&lt;/span>) - mXLevel.get(index); realPosX = mZeroPos[&lt;span class="hljs-number">0&lt;/span>] + index*offsetX + (subValue / offset); &lt;span class="hljs-keyword">break&lt;/span>; } } &lt;span class="hljs-comment">//计算传入的y值与真实屏幕坐标的像素值的百分比差值转换&lt;/span> &lt;span class="hljs-keyword">for&lt;/span> (&lt;span class="hljs-keyword">int&lt;/span> index=&lt;span class="hljs-number">0&lt;/span>; index&lt;mGridLevel; index++) { &lt;span class="hljs-keyword">if&lt;/span> (mMeasureYpos &gt;= mYLevel.get(index) && mMeasureYpos &lt; mYLevel.get(index+&lt;span class="hljs-number">1&lt;/span>)) { &lt;span class="hljs-keyword">int&lt;/span> subValue = mMeasureYpos - mYLevel.get(index); &lt;span class="hljs-keyword">int&lt;/span> offset = mYLevel.get(index+&lt;span class="hljs-number">1&lt;/span>) - mYLevel.get(index); realPosY = mZeroPos[&lt;span class="hljs-number">1&lt;/span>] - index*offsetY - (offsetY - (subValue / offset)); &lt;span class="hljs-keyword">break&lt;/span>; } } &lt;span class="hljs-comment">//画我们传入的坐标点的标记小红点&lt;/span> mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(realPosX, realPosY, &lt;span class="hljs-number">8&lt;/span>, mPaint); &lt;span class="hljs-keyword">int&lt;/span>[] centerPos = {mZeroPos[&lt;span class="hljs-number">0&lt;/span>] + mRealWidth/&lt;span class="hljs-number">2&lt;/span>, mZeroPos[&lt;span class="hljs-number">1&lt;/span>] - mRealHight/&lt;span class="hljs-number">2&lt;/span>}; mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); RectF rectF = &lt;span class="hljs-keyword">null&lt;/span>; Path path = &lt;span class="hljs-keyword">new&lt;/span> Path(); &lt;span class="hljs-comment">//画红点旁边的提示框和文字,有四个区域,然后提示框的小三角指标方位不同&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &lt;= centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &gt;= centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//left-bottom&lt;/span> &lt;span class="hljs-comment">//画三角形&lt;/span> path.moveTo(realPosX+&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY-&lt;span class="hljs-number">15&lt;/span>); &lt;span class="hljs-comment">//画矩形背景&lt;/span> rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY-&lt;span class="hljs-number">40&lt;/span>, realPosX+&lt;span class="hljs-number">200&lt;/span>, realPosY + &lt;span class="hljs-number">30&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); &lt;span class="hljs-comment">//画提示框的文字&lt;/span> mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX+&lt;span class="hljs-number">30&lt;/span>, realPosY, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &lt;= centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &lt; centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//left-top&lt;/span> path.moveTo(realPosX+&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX + &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX+&lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">20&lt;/span>, realPosX+&lt;span class="hljs-number">200&lt;/span>, realPosY + &lt;span class="hljs-number">50&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX+&lt;span class="hljs-number">30&lt;/span>, realPosY+&lt;span class="hljs-number">20&lt;/span>, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &gt; centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &gt;= centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//right-bottom&lt;/span> path.moveTo(realPosX-&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX - &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX-&lt;span class="hljs-number">200&lt;/span>, realPosY-&lt;span class="hljs-number">40&lt;/span>, realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY + &lt;span class="hljs-number">30&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX-&lt;span class="hljs-number">180&lt;/span>, realPosY, mPaint); } &lt;span class="hljs-keyword">else&lt;/span> &lt;span class="hljs-keyword">if&lt;/span> (realPosX &gt; centerPos[&lt;span class="hljs-number">0&lt;/span>] && realPosY &lt; centerPos[&lt;span class="hljs-number">1&lt;/span>]) { &lt;span class="hljs-comment">//right-top&lt;/span> path.moveTo(realPosX-&lt;span class="hljs-number">5&lt;/span>, realPosY+&lt;span class="hljs-number">5&lt;/span>); path.lineTo(realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY+&lt;span class="hljs-number">15&lt;/span>); path.lineTo(realPosX - &lt;span class="hljs-number">15&lt;/span>, realPosY - &lt;span class="hljs-number">15&lt;/span>); rectF = &lt;span class="hljs-keyword">new&lt;/span> RectF(realPosX-&lt;span class="hljs-number">200&lt;/span>, realPosY - &lt;span class="hljs-number">20&lt;/span>, realPosX-&lt;span class="hljs-number">15&lt;/span>, realPosY + &lt;span class="hljs-number">50&lt;/span>); canvas.drawRoundRect(rectF, &lt;span class="hljs-number">15&lt;/span>, &lt;span class="hljs-number">15&lt;/span>, mPaint); mPaint.reset(); mPaint.setColor(Color.RED); mPaint.setTextSize(mXYTitleTextSize - &lt;span class="hljs-number">5&lt;/span>); canvas.drawText(&lt;span class="hljs-string">"("&lt;/span>+mMeasureXpos+&lt;span class="hljs-string">", "&lt;/span>+mMeasureYpos+&lt;span class="hljs-string">")"&lt;/span>, realPosX-&lt;span class="hljs-number">180&lt;/span>, realPosY+&lt;span class="hljs-number">30&lt;/span>, mPaint); } path.close(); mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); canvas.drawPath(path, mPaint); } &lt;span class="hljs-comment">//设置当前比值&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">updateValues&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> x, &lt;span class="hljs-keyword">int&lt;/span> y) { mMeasureXpos = x; mMeasureYpos = y; postInvalidate(); } &lt;span class="hljs-comment">//设置XY轴顶角的title字体大小&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">setTitleTextSize&lt;/span>(&lt;span class="hljs-keyword">int&lt;/span> size) { mXYTitleTextSize = size; } &lt;span class="hljs-comment">//初始化X轴的坐标区间点值,可以不均等分&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initXLevelOffset&lt;/span>(ArrayList&lt;Integer&gt; list) { mXLevel.clear(); mXLevel.addAll(list); } &lt;span class="hljs-comment">//初始化Y轴的坐标区间点值,可以不均等分&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initYLevelOffset&lt;/span>(ArrayList&lt;Integer&gt; list) { mYLevel.clear(); mYLevel.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的提示文字,如果不想显示可以设置""&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridLevelText&lt;/span>(ArrayList&lt;String&gt; list) { mGridLevelText.clear(); mGridLevelText.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的颜色&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridColorLevel&lt;/span>(ArrayList&lt;Integer&gt; list) { mGridColorLevel.clear(); mGridColorLevel.addAll(list); } &lt;span class="hljs-comment">//初始化每个区间的提示文字颜色&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initGridTxtColorLevel&lt;/span>(ArrayList&lt;Integer&gt; list) { mGridTxtColorLevel.clear(); mGridTxtColorLevel.addAll(list); } &lt;span class="hljs-comment">//初始化XY轴title&lt;/span> &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">initTitleXY&lt;/span>(String x, String y) { mTitleX = x; mTitleY = y; } }` 再来看下布局文件: ...

2015年6月6日 · 7 分钟 · 天边的星星

听FackBook工程师讲Custom ViewGroups

原文链接 : [Custom ViewGroups][1] 原文作者 : [Sriram Ramani][2] Android提供了几个ViewGroups如LinearLayout, RelativeLayout, FrameLayout来固定child Views的位置。在这些普通的ViewGroups中有多种使用选择。 例如:LinearLayout几乎支持HTML Flexbox的所有特性(除了包装)。在view之间你可以选择是否显示分割线(dividers),并且基于最大的child测量所有的children。RelativeLayout是一种限制性的解决方案。这些layouts都已经足够好了,但是当你的UI非常复杂的时候它们还能很好的解决么? ViewGroup with a ProfilePhoto, Title, Subtitle and Menu button. 上面的这种布局在Facebook app中是非常常见的。有头像、其它的view垂直摆在它的右侧、还有一个可选操作的view在最右边。这个布局可以通过使用LinearLayout嵌套或者一个RelativeLayout这样的普通ViewGroup实现。我们看一下当分别使用这两种布局的情况下在measure时会发生什么。 使用LinearLayout完成布局的示例 ``` <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <span class="pl-k">&lt;</span><span class="pl-smi">ProfilePhoto</span> android<span class="pl-k">:</span>layout_width<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>40dp<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>layout_height<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>40dp<span class="pl-pds">"</span></span><span class="pl-k">/</span><span class="pl-k">&gt;</span> <span class="pl-k">&lt;</span><span class="pl-smi">LinearLayout</span> android<span class="pl-k">:</span>layout_width<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>0dp<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>layout_height<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>wrap_content<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>layout_weight<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>1<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>orientation<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>vertical<span class="pl-pds">"</span></span><span class="pl-k">&gt;</span> <span class="pl-k">&lt;</span><span class="pl-smi">Title</span> android<span class="pl-k">:</span>layout_width<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>match_parent<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>layout_height<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>wrap_content<span class="pl-pds">"</span></span><span class="pl-k">/</span><span class="pl-k">&gt;</span> <span class="pl-k">&lt;</span><span class="pl-smi">Subtitle</span> android<span class="pl-k">:</span>layout_width<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>match_parent<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>layout_height<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>wrap_content<span class="pl-pds">"</span></span><span class="pl-k">/</span><span class="pl-k">&gt;</span> <span class="pl-k">&lt;</span><span class="pl-k">/</span><span class="pl-smi">LinearLayout</span><span class="pl-k">&gt;</span> <span class="pl-k">&lt;</span><span class="pl-smi">Menu</span> android<span class="pl-k">:</span>layout_width<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>20dp<span class="pl-pds">"</span></span> android<span class="pl-k">:</span>layout_height<span class="pl-k">=</span><span class="pl-s"><span class="pl-pds">"</span>20dp<span class="pl-pds">"</span></span><span class="pl-k">/</span><span class="pl-k">&gt;</span> </LinearLayout> ...

2015年5月27日 · 5 分钟 · 天边的星星

Android自定义控件系列之应用篇——圆形进度条

**一、概述** 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇。链接: http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将在基础篇的基础上,再通过重写ondraw()方法和自定义属性实现圆形进度条,效果如图所示: **二、实现步骤** 1、 编写自定义组件MyCircleProgress扩展View <span class="keyword">public</span> <span class="class"><span class="keyword">class</span></span><span class="class"> <span class="title">MyCircleProgress</span> <span class="keyword">extends</span> <span class="title">View</span> {</span> … } 2、 在MyCircleProgress类中,定制属性 <span class="keyword">public</span> <span class="keyword">int</span> progress = <span class="number"></span>;<span class="comment">//</span><span class="comment">进度实际值,当前进度</span> <span class="javadoc">/**</span><span class="javadoc"> * 自定义控件属性,可灵活的设置圆形进度条的大小、颜色、类型等 */</span> <span class="keyword">private</span> <span class="keyword">int</span> mR;<span class="comment">//</span><span class="comment">圆半径,决定圆大小</span> <span class="keyword">private</span> <span class="keyword">int</span> bgColor;<span class="comment">//</span><span class="comment">圆或弧的背景颜色</span> <span class="keyword">private</span> <span class="keyword">int</span> fgColor;<span class="comment">//</span><span class="comment">圆或弧的前景颜色,即绘制时的颜色</span> <span class="keyword">private</span> <span class="keyword">int</span> drawStyle; <span class="comment">//</span><span class="comment">绘制类型 FILL画圆形进度条,STROKE绘制弧形进度条</span> <span class="keyword">private</span> <span class="keyword">int</span> strokeWidth;<span class="comment">//</span><span class="comment">STROKE绘制弧形的弧线的宽度</span> <span class="keyword">private</span> <span class="keyword">int</span> max;<span class="comment">//</span><span class="comment">最大值,设置进度的最大值</span> <span class="javadoc">/**</span><span class="javadoc"> * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 */</span> <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> setProgress(<span class="keyword">int</span> progress) { <span class="keyword">if</span>(progress&lt;<span class="number"></span>){ progress=<span class="number"></span>; }<span class="keyword">else</span> <span class="keyword">if</span>(progress&gt;max){ progress=max; }<span class="keyword">else</span>{ <span class="keyword">this</span>.progress = progress; } } <span class="keyword">public</span> <span class="keyword">int</span> getMax() { <span class="keyword">return</span> max; } 3、 为定制的属性编写attrs.xml资源,该资源文件放在res/values目录下,内容如下: ...

2015年3月31日 · 3 分钟 · 天边的星星

Android自定义控件实战——仿多看阅读平移翻页

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38728119 之前自己做的一个APP需要用到翻页阅读,网上看过立体翻页效果,不过bug太多了还不兼容。看了一下多看阅读翻页是采用平移翻页的,于是就仿写了一个平移翻页的控件。效果如下: 在翻页时页面右边缘绘制了阴影,效果还不错。要实现这种平移翻页控件并不难,只需要定义一个布局管理页面就可以了。具体实现上有以下难点: 1、循环翻页,页面的重复利用。 2、在翻页时过滤掉多点触碰。 3、采用setAdapter的方式设置页面布局和数据。 下面就来一一解决这几个难点。首先看循环翻页问题,怎么样能采用较少的页面实现这种翻页呢?由于屏幕上每次只能显示一张完整的页面,翻过去的页面也看不到,所以可以把翻过去的页面拿来重复利用,不必每次都new一个页面,所以,我只用了三张页面实现循环翻页。要想重复利用页面,首先要知道页面在布局中序号和对应的层次关系,比如一个父控件的子view的序号越大就位于越上层。循环利用页面的原理图如下: 向右翻页时状态图是这样的,只用了0、1、2三张页面,页面序号为2的位于最上层,我把它隐藏在左边,所以看到的只有页面1,页面0在1下面挡着也看不到,向右翻页时,页面2被滑到屏幕中,这时候把页面0的内容替换成页面2的前一页内容,把它放到之前页面2的位置,这时,状态又回到了初始状态,又可以继续向右翻页了! 向左翻页时是这样的,初始状态还是一样,当页面1被往左翻过时,看到的是页面0,这时候页面0下面已经没有页面了,而页面2已经用不到了,这时候把页面2放到页面0下面,这时候状态又回到了初始状态,就可以继续往左翻页了。 类似于这种循环效果的实现我一直用的解决方案都是将选中的置于最中间,比如原理图中的页面1,每次翻页完成后可见的都是页面1。在滚动选择器PickerView中也是同样的方案。这就解决了页面的重复利用问题了。 解决难点2 翻页时过滤多点触碰这个问题在仿淘宝商品浏览界面中已经解决过了,就是用一个控制变量mEvents过滤掉pointer down或up后到来的第一个move事件。 解决难点3 采用adapter方式设置页面的布局和数据。这个在Android的AdapterView里用到的,但是我没有看它的adapter机制,太复杂了,我就搞了个简单的adapter,如下: PageAdapter.java: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38728119#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38728119#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.pagerdemo; - - <span class="keyword">import</span> android.view.View; - - <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> PageAdapter - { - <span class="comment">/**</span> - <span class="comment"> * @return 页面view</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">abstract</span> View getView(); - - <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">int</span> getCount(); - - <span class="comment">/**</span> - <span class="comment"> * 将内容添加到view中</span> - <span class="comment"> * </span> - <span class="comment"> * @param view</span> - <span class="comment"> * 包含内容的view</span> - <span class="comment"> * @param position</span> - <span class="comment"> * 第position页</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> addContent(View view, <span class="keyword">int</span> position); - } 这是一个抽象类,getView()用于返回页面的布局,getCount()返回数据总共需要多少页,addContent(View view, int position)这个是每翻过一页后将会被调用来请求页面数据的,参数view就是页面,position是表明第几页。待会儿会在自定义布局中定义setAdapter方法设置设配器。 ...

2015年3月4日 · 13 分钟 · 天边的星星

Android自定义控件实战——水流波动效果的实现WaveView

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38556891 水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它来绘制Path曲线。待会儿再讲解2阶的贝塞尔曲线是怎么回事,先来看实现的效果: 这个波长比较短,还看不到起伏,只是荡漾,把波长拉长再看一下: 已经可以看到起伏很明显了,再拉长看一下: 这个的起伏感就比较强了。利用这个波动效果,可以用在绘制水位线的时候使用到,还可以做一个波动的进度条WaveUpProgress,比如这样: 是不是很动感? 那这样的波动效果是怎么做的呢?前面讲到的贝塞尔曲线到底是什么呢?下面一一讲解。想要用好贝塞尔曲线就得先理解它的表达式,为了形象描述,我从网上盗了些动图。 首先看1阶贝塞尔曲线的表达式: 随着t的变化,它实际是一条P0到P1的直线段: Android中Path的quadTo是3点的2阶贝塞尔曲线,那么2阶的表达式是这样的: 看起来很复杂,我把它拆分开来看: 然后再合并成这样: 看到什么了吧?如果看不出来再替换成这样: B0和B1分别是P0到P1和P1到P2的1阶贝塞尔曲线。而2阶贝塞尔曲线B就是B0到B1的1阶贝塞尔曲线。显然,它的动态图表示出来就不难理解了: 红色点的运动轨迹就是B的轨迹,这就是2阶贝塞尔曲线了。当P1位于P0和P2的垂直平分线上时,B就是开口向上或向下的抛物线了。而在WaveView中就是用的开口向上和向下的抛物线模拟水波。在Android里用Path的方法,首先path.moveTo(P0),然后path.quadTo(P1, P2),canvas.drawPath(path, paint)曲线就出来了,如果想要绘制多个贝塞尔曲线就不断的quadTo吧。 讲完贝塞尔曲线后就要开始讲水波动的效果是怎么来的了,首先要理解,机械波的传输就是通过介质的震动把波形往传输方向平移,每震动一个周期波形刚好平移一个波长,所有介质点又回到一个周期前的状态。所以要实现水波动效果只需要把波形平移就可以了。 那么WaveView的实现原理是这样的: 首先在View上根据View宽计算可以容纳几个完整波形,不够一个的算一个,然后在View的不可见处预留一个完整的波形;然后波动开始的时候将所有点同时在x方向上移动相同的距离,这样隐藏的波形就会被平移出来,当平移距离达到一个波长时,这时候将所有点的x坐标又恢复到平移前的值,这样就可以一个波形一个波形地往外传输。用草图表示如下: WaveView的原理在上图很直观的看出来了,P[2n+1],n>=0都是贝塞尔曲线的控制点,红线为水位线。 知道原理以后可以看代码了: WaveView.java: **[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38556891#) <div> <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1"> </embed> </div> </div> - <span class="keyword">package</span> com.jingchen.waveview; - - <span class="keyword">import</span> java.util.ArrayList; - <span class="keyword">import</span> java.util.List; - <span class="keyword">import</span> java.util.Timer; - <span class="keyword">import</span> java.util.TimerTask; - - <span class="keyword">import</span> android.content.Context; - <span class="keyword">import</span> android.graphics.Canvas; - <span class="keyword">import</span> android.graphics.Color; - <span class="keyword">import</span> android.graphics.Paint; - <span class="keyword">import</span> android.graphics.Paint.Align; - <span class="keyword">import</span> android.graphics.Paint.Style; - <span class="keyword">import</span> android.graphics.Region.Op; - <span class="keyword">import</span> android.graphics.Path; - <span class="keyword">import</span> android.graphics.RectF; - <span class="keyword">import</span> android.os.Handler; - <span class="keyword">import</span> android.os.Message; - <span class="keyword">import</span> android.util.AttributeSet; - <span class="keyword">import</span> android.view.View; - - <span class="comment">/**</span> - <span class="comment"> * 水流波动控件</span> - <span class="comment"> * </span> - <span class="comment"> * @author chenjing</span> - <span class="comment"> * </span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">class</span> WaveView <span class="keyword">extends</span> View - { - - <span class="keyword">private</span> <span class="keyword">int</span> mViewWidth; - <span class="keyword">private</span> <span class="keyword">int</span> mViewHeight; - - <span class="comment">/**</span> - <span class="comment"> * 水位线</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mLevelLine; - - <span class="comment">/**</span> - <span class="comment"> * 波浪起伏幅度</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mWaveHeight = <span class="number">80</span>; - <span class="comment">/**</span> - <span class="comment"> * 波长</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mWaveWidth = <span class="number">200</span>; - <span class="comment">/**</span> - <span class="comment"> * 被隐藏的最左边的波形</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">float</span> mLeftSide; - - <span class="keyword">private</span> <span class="keyword">float</span> mMoveLen; - <span class="comment">/**</span> - <span class="comment"> * 水波平移速度</span> - <span class="comment"> */</span> - <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> SPEED = <span class="number">1</span>.7f; - - <span class="keyword">private</span> List<Point> mPointsList; - <span class="keyword">private</span> Paint mPaint; - <span class="keyword">private</span> Paint mTextPaint; - <span class="keyword">private</span> Path mWavePath; - <span class="keyword">private</span> <span class="keyword">boolean</span> isMeasured = <span class="keyword">false</span>; - - <span class="keyword">private</span> Timer timer; - <span class="keyword">private</span> MyTimerTask mTask; - Handler updateHandler = <span class="keyword">new</span> Handler() - { - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg) - { - <span class="comment">// 记录平移总位移</span> - mMoveLen += SPEED; - <span class="comment">// 水位上升</span> - mLevelLine -= <span class="number"></span>.1f; - <span class="keyword">if</span> (mLevelLine < <span class="number"></span>) - mLevelLine = <span class="number"></span>; - mLeftSide += SPEED; - <span class="comment">// 波形平移</span> - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < mPointsList.size(); i++) - { - mPointsList.get(i).setX(mPointsList.get(i).getX() + SPEED); - <span class="keyword">switch</span> (i % <span class="number">4</span>) - { - <span class="keyword">case</span> <span class="number"></span>: - <span class="keyword">case</span> <span class="number">2</span>: - mPointsList.get(i).setY(mLevelLine); - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">1</span>: - mPointsList.get(i).setY(mLevelLine + mWaveHeight); - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">3</span>: - mPointsList.get(i).setY(mLevelLine &#8211; mWaveHeight); - <span class="keyword">break</span>; - } - } - <span class="keyword">if</span> (mMoveLen >= mWaveWidth) - { - <span class="comment">// 波形平移超过一个完整波形后复位</span> - mMoveLen = <span class="number"></span>; - resetPoints(); - } - invalidate(); - } - - }; - - <span class="comment">/**</span> - <span class="comment"> * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态</span> - <span class="comment"> */</span> - <span class="keyword">private</span> <span class="keyword">void</span> resetPoints() - { - mLeftSide = -mWaveWidth; - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < mPointsList.size(); i++) - { - mPointsList.get(i).setX(i * mWaveWidth / <span class="number">4</span> &#8211; mWaveWidth); - } - } - - <span class="keyword">public</span> WaveView(Context context) - { - <span class="keyword">super</span>(context); - init(); - } - - <span class="keyword">public</span> WaveView(Context context, AttributeSet attrs) - { - <span class="keyword">super</span>(context, attrs); - init(); - } - - <span class="keyword">public</span> WaveView(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyle) - { - <span class="keyword">super</span>(context, attrs, defStyle); - init(); - } - - <span class="keyword">private</span> <span class="keyword">void</span> init() - { - mPointsList = <span class="keyword">new</span> ArrayList<Point>(); - timer = <span class="keyword">new</span> Timer(); - - mPaint = <span class="keyword">new</span> Paint(); - mPaint.setAntiAlias(<span class="keyword">true</span>); - mPaint.setStyle(Style.FILL); - mPaint.setColor(Color.BLUE); - - mTextPaint = <span class="keyword">new</span> Paint(); - mTextPaint.setColor(Color.WHITE); - mTextPaint.setTextAlign(Align.CENTER); - mTextPaint.setTextSize(<span class="number">30</span>); - - mWavePath = <span class="keyword">new</span> Path(); - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> onWindowFocusChanged(<span class="keyword">boolean</span> hasWindowFocus) - { - <span class="keyword">super</span>.onWindowFocusChanged(hasWindowFocus); - <span class="comment">// 开始波动</span> - start(); - } - - <span class="keyword">private</span> <span class="keyword">void</span> start() - { - <span class="keyword">if</span> (mTask != <span class="keyword">null</span>) - { - mTask.cancel(); - mTask = <span class="keyword">null</span>; - } - mTask = <span class="keyword">new</span> MyTimerTask(updateHandler); - timer.schedule(mTask, <span class="number"></span>, <span class="number">10</span>); - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec) - { - <span class="keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec); - <span class="keyword">if</span> (!isMeasured) - { - isMeasured = <span class="keyword">true</span>; - mViewHeight = getMeasuredHeight(); - mViewWidth = getMeasuredWidth(); - <span class="comment">// 水位线从最底下开始上升</span> - mLevelLine = mViewHeight; - <span class="comment">// 根据View宽度计算波形峰值</span> - mWaveHeight = mViewWidth / <span class="number">2</span>.5f; - <span class="comment">// 波长等于四倍View宽度也就是View中只能看到四分之一个波形,这样可以使起伏更明显</span> - mWaveWidth = mViewWidth * <span class="number">4</span>; - <span class="comment">// 左边隐藏的距离预留一个波形</span> - mLeftSide = -mWaveWidth; - <span class="comment">// 这里计算在可见的View宽度中能容纳几个波形,注意n上取整</span> - <span class="keyword">int</span> n = (<span class="keyword">int</span>) Math.round(mViewWidth / mWaveWidth + <span class="number">0.5</span>); - <span class="comment">// n个波形需要4n+1个点,但是我们要预留一个波形在左边隐藏区域,所以需要4n+5个点</span> - <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < (<span class="number">4</span> * n + <span class="number">5</span>); i++) - { - <span class="comment">// 从P0开始初始化到P4n+4,总共4n+5个点</span> - <span class="keyword">float</span> x = i * mWaveWidth / <span class="number">4</span> &#8211; mWaveWidth; - <span class="keyword">float</span> y = <span class="number"></span>; - <span class="keyword">switch</span> (i % <span class="number">4</span>) - { - <span class="keyword">case</span> <span class="number"></span>: - <span class="keyword">case</span> <span class="number">2</span>: - <span class="comment">// 零点位于水位线上</span> - y = mLevelLine; - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">1</span>: - <span class="comment">// 往下波动的控制点</span> - y = mLevelLine + mWaveHeight; - <span class="keyword">break</span>; - <span class="keyword">case</span> <span class="number">3</span>: - <span class="comment">// 往上波动的控制点</span> - y = mLevelLine &#8211; mWaveHeight; - <span class="keyword">break</span>; - } - mPointsList.add(<span class="keyword">new</span> Point(x, y)); - } - } - } - - <span class="annotation">@Override</span> - <span class="keyword">protected</span> <span class="keyword">void</span> onDraw(Canvas canvas) - { - - mWavePath.reset(); - <span class="keyword">int</span> i = <span class="number"></span>; - mWavePath.moveTo(mPointsList.get(<span class="number"></span>).getX(), mPointsList.get(<span class="number"></span>).getY()); - <span class="keyword">for</span> (; i < mPointsList.size() &#8211; <span class="number">2</span>; i = i + <span class="number">2</span>) - { - mWavePath.quadTo(mPointsList.get(i + <span class="number">1</span>).getX(), - mPointsList.get(i + <span class="number">1</span>).getY(), mPointsList.get(i + <span class="number">2</span>) - .getX(), mPointsList.get(i + <span class="number">2</span>).getY()); - } - mWavePath.lineTo(mPointsList.get(i).getX(), mViewHeight); - mWavePath.lineTo(mLeftSide, mViewHeight); - mWavePath.close(); - - <span class="comment">// mPaint的Style是FILL,会填充整个Path区域</span> - canvas.drawPath(mWavePath, mPaint); - <span class="comment">// 绘制百分比</span> - canvas.drawText(<span class="string">&#8220;&#8221;</span> + ((<span class="keyword">int</span>) ((<span class="number">1</span> &#8211; mLevelLine / mViewHeight) * <span class="number">100</span>)) - + <span class="string">&#8220;%&#8221;</span>, mViewWidth / <span class="number">2</span>, mLevelLine + mWaveHeight - + (mViewHeight &#8211; mLevelLine &#8211; mWaveHeight) / <span class="number">2</span>, mTextPaint); - } - - <span class="keyword">class</span> MyTimerTask <span class="keyword">extends</span> TimerTask - { - Handler handler; - - <span class="keyword">public</span> MyTimerTask(Handler handler) - { - <span class="keyword">this</span>.handler = handler; - } - - <span class="annotation">@Override</span> - <span class="keyword">public</span> <span class="keyword">void</span> run() - { - handler.sendMessage(handler.obtainMessage()); - } - - } - - <span class="keyword">class</span> Point - { - <span class="keyword">private</span> <span class="keyword">float</span> x; - <span class="keyword">private</span> <span class="keyword">float</span> y; - - <span class="keyword">public</span> <span class="keyword">float</span> getX() - { - <span class="keyword">return</span> x; - } - - <span class="keyword">public</span> <span class="keyword">void</span> setX(<span class="keyword">float</span> x) - { - <span class="keyword">this</span>.x = x; - } - - <span class="keyword">public</span> <span class="keyword">float</span> getY() - { - <span class="keyword">return</span> y; - } - - <span class="keyword">public</span> <span class="keyword">void</span> setY(<span class="keyword">float</span> y) - { - <span class="keyword">this</span>.y = y; - } - - <span class="keyword">public</span> Point(<span class="keyword">float</span> x, <span class="keyword">float</span> y) - { - <span class="keyword">this</span>.x = x; - <span class="keyword">this</span>.y = y; - } - - } - - } 代码中注释写的很多,不难看懂。 ...

2015年3月4日 · 7 分钟 · 天边的星星