|
| 1 | +# 自定义View入门 |
| 2 | + |
| 3 | +> 在Android应用开发过程中,固定的一些控件和属性可能满足不了开发的需求,所以在一些特殊情况下,我们需要自定义控件与属性。 |
| 4 | +
|
| 5 | +# 一、实现步骤 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +1. 继承View类或其子类 |
| 10 | + |
| 11 | +2. 复写view中的一些函数 |
| 12 | + |
| 13 | +3. 为自定义View类增加属性(两种方式) |
| 14 | + |
| 15 | +4. 绘制控件(导入布局) |
| 16 | + |
| 17 | +5. 响应用户事件 |
| 18 | + |
| 19 | +6. 定义回调函数(根据自己需求来选择) |
| 20 | + |
| 21 | +# 二、哪些方法需要被重写 |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | + - ``onDraw()`` |
| 26 | + |
| 27 | + view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的绘制。对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的(但必须实现dispatchDraw()函数,告诉子view绘制自己)。 |
| 28 | + |
| 29 | + - ``onLayout()`` |
| 30 | + |
| 31 | + 主要是为viewGroup类型布局子视图用的,在View中这个函数为空函数。 |
| 32 | + |
| 33 | + - ``onMeasure()`` |
| 34 | + |
| 35 | + 用于计算视图大小(即长和宽)的方式,并通过setMeasuredDimension(width, height)保存计算结果。 |
| 36 | + |
| 37 | + - ``onTouchEvent`` |
| 38 | + |
| 39 | + 定义触屏事件来响应用户操作。 |
| 40 | + |
| 41 | + |
| 42 | +---- |
| 43 | + |
| 44 | +还有一些不常用的方法: |
| 45 | + |
| 46 | +``onKeyDown()`` 当按下某个键盘时 |
| 47 | + |
| 48 | +``onKeyUp()`` 当松开某个键盘时 |
| 49 | + |
| 50 | +``onTrackballEvent()`` 当发生轨迹球事件时 |
| 51 | + |
| 52 | +``onSizeChange()`` 当该组件的大小被改变时 |
| 53 | + |
| 54 | +``onFinishInflate()`` 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法 |
| 55 | + |
| 56 | +``onWindowFocusChanged(boolean)`` 当该组件得到、失去焦点时 |
| 57 | + |
| 58 | +``onAttachedToWindow()`` 当把该组件放入到某个窗口时 |
| 59 | + |
| 60 | +``onDetachedFromWindow()`` 当把该组件从某个窗口上分离时触发的方法 |
| 61 | + |
| 62 | +``onWindowVisibilityChanged(int)`` 当包含该组件的窗口的可见性发生改变时触发的方法 |
| 63 | + |
| 64 | +**View的绘制流程** |
| 65 | +绘制流程函数调用关系如下图: |
| 66 | + |
| 67 | + |
| 68 | + |
| 69 | +我们调用requestLayout()的时候,会触发measure 和 layout 过程,调用invalidate,会执行 draw 过程。 |
| 70 | + |
| 71 | +# 三.自定义控件的三种方式 |
| 72 | + |
| 73 | + |
| 74 | + |
| 75 | + 1. 继承已有的控件 |
| 76 | + |
| 77 | + 当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。 |
| 78 | + |
| 79 | + 2. 继承一个布局文件 |
| 80 | + |
| 81 | + 一般用于自定义组合控件,在构造函数中通过inflater和addView()方法加载自定义控件的布局文件形成图形界面(不需要onDraw方法)。 |
| 82 | + |
| 83 | +3.继承view |
| 84 | + |
| 85 | + 通过onDraw方法来绘制出组件界面。 |
| 86 | + |
| 87 | +# 四.自定义属性的两种方法 |
| 88 | + |
| 89 | +1.在布局文件中直接加入属性,在构造函数中去获得。 |
| 90 | + |
| 91 | + |
| 92 | +布局文件: |
| 93 | + |
| 94 | +``` |
| 95 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 96 | + android:layout_width="match_parent" |
| 97 | + android:layout_height="match_parent" |
| 98 | + > |
| 99 | + <com.example.demo.myView |
| 100 | + android:layout_width="wrap_content" |
| 101 | + android:layout_height="wrap_content" |
| 102 | + Text="@string/hello_world" |
| 103 | + /> |
| 104 | +</RelativeLayout> |
| 105 | +
|
| 106 | +``` |
| 107 | +获取属性值: |
| 108 | + |
| 109 | +``` |
| 110 | +public myView(Context context, AttributeSet attrs) { |
| 111 | + super(context, attrs); |
| 112 | + // TODO Auto-generated constructor stub |
| 113 | +int textId = attrs.getAttributeResourceValue(null, "Text", 0); |
| 114 | +String text = context.getResources().getText(textId).toString(); |
| 115 | + } |
| 116 | +``` |
| 117 | + |
| 118 | +2.在res/values/ 下建立一个attrs.xml 来声明自定义view的属性。 |
| 119 | + |
| 120 | + |
| 121 | +可以定义的属性有: |
| 122 | + |
| 123 | +``` |
| 124 | +<declare-styleable name = "名称"> |
| 125 | +//参考某一资源ID (name可以随便命名) |
| 126 | +<attr name = "background" format = "reference" /> |
| 127 | +//颜色值 |
| 128 | +<attr name = "textColor" format = "color" /> |
| 129 | +//布尔值 |
| 130 | +<attr name = "focusable" format = "boolean" /> |
| 131 | +//尺寸值 |
| 132 | +<attr name = "layout_width" format = "dimension" /> |
| 133 | +//浮点值 |
| 134 | +<attr name = "fromAlpha" format = "float" /> |
| 135 | +//整型值 |
| 136 | +<attr name = "frameDuration" format="integer" /> |
| 137 | +//字符串 |
| 138 | +<attr name = "text" format = "string" /> |
| 139 | +//百分数 |
| 140 | +<attr name = "pivotX" format = "fraction" /> |
| 141 | +
|
| 142 | +//枚举值 |
| 143 | +<attr name="orientation"> |
| 144 | +<enum name="horizontal" value="0" /> |
| 145 | +<enum name="vertical" value="1" /> |
| 146 | +</attr> |
| 147 | +
|
| 148 | +//位或运算 |
| 149 | +<attr name="windowSoftInputMode"> |
| 150 | +<flag name = "stateUnspecified" value = "0" /> |
| 151 | +<flag name = "stateUnchanged" value = "1" /> |
| 152 | +</attr> |
| 153 | +
|
| 154 | +//多类型 |
| 155 | +<attr name = "background" format = "reference|color" /> |
| 156 | +</declare-styleable> |
| 157 | +``` |
| 158 | + |
| 159 | + - attrs.xml进行属性声明 |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | +``` |
| 164 | +<?xml version="1.0" encoding="utf-8"?> |
| 165 | +<resources> |
| 166 | + <declare-styleable name="myView"> |
| 167 | + <attr name="text" format="string"/> |
| 168 | + <attr name="textColor" format="color"/> |
| 169 | + </declare-styleable> |
| 170 | +</resources> |
| 171 | +
|
| 172 | +``` |
| 173 | + |
| 174 | + - 添加到布局文件 |
| 175 | + |
| 176 | + |
| 177 | + |
| 178 | +``` |
| 179 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 180 | + android:layout_width="match_parent" |
| 181 | + android:layout_height="match_parent" |
| 182 | + xmlns:myview="http://schemas.android.com/apk/com.example.demo" |
| 183 | + > |
| 184 | + <com.example.demo.myView |
| 185 | + android:layout_width="wrap_content" |
| 186 | + android:layout_height="wrap_content" |
| 187 | + myview:text = "test" |
| 188 | + myview:textColor ="#ff0000" |
| 189 | + /> |
| 190 | +</RelativeLayout> |
| 191 | +
|
| 192 | +``` |
| 193 | +这里注意命名空间: |
| 194 | +xmlns:前缀="http://schemas.android.com/apk/res/包名(或res-auto)", |
| 195 | + |
| 196 | +前缀:TextColor 使用属性。 |
| 197 | + |
| 198 | + - 在构造函数中获取属性值 |
| 199 | + |
| 200 | + |
| 201 | + |
| 202 | +``` |
| 203 | +public myView(Context context, AttributeSet attrs) { |
| 204 | + super(context, attrs); |
| 205 | + // TODO Auto-generated constructor stub |
| 206 | + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.myView); |
| 207 | + String text = a.getString(R.styleable.myView_text); |
| 208 | + int textColor = a.getColor(R.styleable.myView_textColor, Color.WHITE); |
| 209 | + |
| 210 | + a.recycle(); |
| 211 | + } |
| 212 | +``` |
| 213 | + |
| 214 | + 或者: |
| 215 | + |
| 216 | + |
| 217 | + |
| 218 | +``` |
| 219 | + public myView(Context context, AttributeSet attrs) { |
| 220 | + super(context, attrs); |
| 221 | + // TODO Auto-generated constructor stub |
| 222 | + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.myView); |
| 223 | + int n = a.getIndexCount(); |
| 224 | + for(int i=0;i<n;i++){ |
| 225 | + int attr = a.getIndex(i); |
| 226 | + switch (attr) { |
| 227 | + case R.styleable.myView_text: |
| 228 | + |
| 229 | + break; |
| 230 | +
|
| 231 | + case R.styleable.myView_textColor: |
| 232 | + |
| 233 | + break; |
| 234 | + |
| 235 | + } |
| 236 | + } |
| 237 | + a.recycle(); |
| 238 | + } |
| 239 | +``` |
| 240 | + |
| 241 | +# 五. 自定义随手指移动的小球(小例子) |
| 242 | + |
| 243 | + |
| 244 | +<img src="http://img.blog.csdn.net/20160503143613554" width="210" height="334" /> |
| 245 | + |
| 246 | + |
| 247 | +实现上面的效果我们大致需要分成这几步 |
| 248 | + |
| 249 | + - 在res/values/ 下建立一个attrs.xml 来声明自定义view的属性 |
| 250 | + - 一个继承View并复写部分函数的自定义view的类 |
| 251 | + - 一个展示自定义view 的容器界面 |
| 252 | + |
| 253 | +1.自定义view命名为myView,它有一个属性值,格式为color: |
| 254 | + |
| 255 | +``` |
| 256 | +<?xml version="1.0" encoding="utf-8"?> |
| 257 | +<resources> |
| 258 | + <declare-styleable name="myView"> |
| 259 | + <attr name="TextColor" format="color"/> |
| 260 | + </declare-styleable> |
| 261 | +</resources> |
| 262 | +``` |
| 263 | + |
| 264 | +2.在构造函数获取获得view的属性配置和复写onDraw和onTouchEvent函数实现绘制界面和用户事件响应。 |
| 265 | + |
| 266 | +``` |
| 267 | +public class myView extends View{ |
| 268 | + //定义画笔和初始位置 |
| 269 | + Paint p = new Paint(); |
| 270 | + public float currentX = 50; |
| 271 | + public float currentY = 50; |
| 272 | + public int textColor; |
| 273 | +
|
| 274 | + public myView(Context context, AttributeSet attrs) { |
| 275 | + super(context, attrs); |
| 276 | + //获取资源文件里面的属性,由于这里只有一个属性值,不用遍历数组,直接通过R文件拿出color值 |
| 277 | + //把属性放在资源文件里,方便设置和复用 |
| 278 | + TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.myView); |
| 279 | + textColor = array.getColor(R.styleable.myView_TextColor,Color.BLACK); |
| 280 | + array.recycle(); |
| 281 | + } |
| 282 | +
|
| 283 | + @Override |
| 284 | + protected void onDraw(Canvas canvas) { |
| 285 | + super.onDraw(canvas); |
| 286 | + //画一个蓝色的圆形 |
| 287 | + p.setColor(Color.BLUE); |
| 288 | + canvas.drawCircle(currentX,currentY,30,p); |
| 289 | + //设置文字和颜色,这里的颜色是资源文件values里面的值 |
| 290 | + p.setColor(textColor); |
| 291 | + canvas.drawText("BY finch",currentX-30,currentY+50,p); |
| 292 | + } |
| 293 | +
|
| 294 | + @Override |
| 295 | + public boolean onTouchEvent(MotionEvent event) { |
| 296 | +
|
| 297 | +
|
| 298 | + currentX = event.getX(); |
| 299 | + currentY = event.getY(); |
| 300 | + invalidate();//重新绘制图形 |
| 301 | + return true; |
| 302 | + } |
| 303 | +} |
| 304 | +``` |
| 305 | + |
| 306 | + 这里通过不断的更新当前位置坐标和重新绘制图形实现效果,要注意的是使用TypedArray后一定要记得recycle(). 否则会对下次调用产生影响。 |
| 307 | +  |
| 308 | + |
| 309 | + |
| 310 | +3.把myView加入到activity_main.xml布局里面 |
| 311 | + |
| 312 | + |
| 313 | + |
| 314 | +``` |
| 315 | +<?xml version="1.0" encoding="utf-8"?> |
| 316 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 317 | + xmlns:tools="http://schemas.android.com/tools" |
| 318 | + android:layout_width="match_parent" |
| 319 | + android:layout_height="match_parent" |
| 320 | + xmlns:myview="http://schemas.android.com/apk/res-auto" |
| 321 | + android:paddingBottom="@dimen/activity_vertical_margin" |
| 322 | + android:paddingLeft="@dimen/activity_horizontal_margin" |
| 323 | + android:paddingRight="@dimen/activity_horizontal_margin" |
| 324 | + android:paddingTop="@dimen/activity_vertical_margin" |
| 325 | + tools:context="finch.scu.cn.myview.MainActivity"> |
| 326 | +
|
| 327 | +
|
| 328 | + <finch.scu.cn.myview.myView |
| 329 | + android:layout_width="match_parent" |
| 330 | + android:layout_height="match_parent" |
| 331 | + myview:TextColor="#ff0000" |
| 332 | + /> |
| 333 | +</RelativeLayout> |
| 334 | +``` |
| 335 | + |
| 336 | +4.最后是MainActivity |
| 337 | + |
| 338 | +``` |
| 339 | +public class MainActivity extends AppCompatActivity { |
| 340 | +
|
| 341 | + @Override |
| 342 | + protected void onCreate(Bundle savedInstanceState) { |
| 343 | + super.onCreate(savedInstanceState); |
| 344 | + setContentView(R.layout.activity_main); |
| 345 | + } |
| 346 | +} |
| 347 | +``` |
| 348 | + |
| 349 | + - 具体的view要根据具体的需求来,比如我们要侧滑删除的listview我们可以继承listview,监听侧滑事件,显示删除按钮实现功能。 |
| 350 | + |
0 commit comments