当前位置:   article > 正文

Android自定义View的三种实现方式及自定义属性_android 自定义view中外部接收数据初始化

android 自定义view中外部接收数据初始化

自定义View的实现方式有以下几种: 组合控件,继承控件,自绘控件

详细可分为:自定义组合控件,继承系统View控件,继承系统ViewGroup,自绘View控件,自会ViewGroup控件

1. 自定义组合控件

组合控件就是将多个控件组合成一个新的控件,可以重复使用。

  1. 1.编写布局文件
  2. 2.实现构造方法
  3. 3.初始化UI
  4. 4.提供对外的方法
  5. 5.在布局当中引用该控件
  6. 6.activity中使用

示例:中间是title的文字,左边是返回按钮,右边是一个添加按钮

  1. //因为我们的布局采用RelativeLayout,所以这里继承RelativeLayout。
  2. //关于各个构造方法的介绍可以参考前面的内容
  3. public class YFHeaderView extends RelativeLayout {
  4. public YFHeaderView(Context context) {
  5. super(context);
  6. }
  7. public YFHeaderView(Context context, AttributeSet attrs) {
  8. super(context, attrs);
  9. }
  10. public YFHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {
  11. super(context, attrs, defStyleAttr);
  12. }
  13. //初始化UI,可根据业务需求设置默认值。
  14. private void initView(Context context) {
  15. LayoutInflater.from(context).inflate(R.layout.view_header, this, true);
  16. img_left = (ImageView) findViewById(R.id.header_left_img);
  17. img_right = (ImageView) findViewById(R.id.header_right_img);
  18. text_center = (TextView) findViewById(R.id.header_center_text);
  19. layout_root = (RelativeLayout) findViewById(R.id.header_root_layout);
  20. layout_root.setBackgroundColor(Color.BLACK);
  21. text_center.setTextColor(Color.WHITE);
  22. }
  23. //设置标题文字的方法
  24. private void setTitle(String title) {
  25. if (!TextUtils.isEmpty(title)) {
  26. text_center.setText(title);
  27. }
  28. }
  29. //对左边按钮设置事件的方法
  30. private void setLeftListener(OnClickListener onClickListener) {
  31. img_left.setOnClickListener(onClickListener);
  32. }
  33. //对右边按钮设置事件的方法
  34. private void setRightListener(OnClickListener onClickListener) {
  35. img_right.setOnClickListener(onClickListener);
  36. }
  37. }

2.继承系统控件

  通过继承系统控件(View子类控件或ViewGroup子类控件)来完成自定义View,一般是希望在原有系统控件基础上做一些修饰性的修改,而不会做大幅度的改动,如在TextView的文字下方添加下划线,在LinearLayout布局中加一个蒙板等。这种方式往往都会复用系统控件的onMeasure和onLayout方法,而只需要重写onDraw方法,在其中绘制一些需要的内容。下面会分别继承View类控件和ViewGroup类控件来举例说明。

2.1、继承View类系统控件

如下示例为在TextView文字下方显示红色下划线,其基本步骤如下:

(1)继承View控件,并重写onDraw方法

  1. @SuppressLint("AppCompatCustomView")
  2. public class UnderlineTextView extends TextView{
  3. public UnderlineTextView(Context context, @Nullable AttributeSet attrs) {
  4. super(context, attrs);
  5. }
  6. @Override
  7. protected void onDraw(Canvas canvas) {
  8. super.onDraw(canvas);
  9. Paint paint = new Paint();
  10. paint.setColor(Color.RED);
  11. paint.setStrokeWidth(5);
  12. int width = getWidth();
  13. int height = getBaseline();
  14. canvas.drawLine(0,height,width,height,paint);
  15. }
  16. }

(2)在布局文件中调用

       就像使用一个普通TextView一样使用UnderlineTextView。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <com.example.demos.customviewdemo.UnderlineTextView
  6. android:layout_width="wrap_content"
  7. android:layout_height="wrap_content"
  8. android:textSize="50dp"
  9. android:layout_centerInParent="true"
  10. android:text="Hello World!"/>
  11. </RelativeLayout>

2.2、继承ViewGroup类系统控件

如下示例演示,在layout布局上添加一个浅红色的半透明蒙板,这种需求在工作中也是非常常见的。

    (1)继承ViewGroup类系统控件

  1. public class ForegroundLinearLayout extends LinearLayout{
  2. public ForegroundLinearLayout(Context context, @Nullable AttributeSet attrs) {
  3. super(context, attrs);
  4. }
  5. @Override
  6. protected void dispatchDraw(Canvas canvas) {
  7. super.dispatchDraw(canvas);
  8. canvas.drawColor(Color.parseColor("#50FF0000"));
  9. }
  10. }

(2)在布局文件中调用

       对ForegroundLinearLayout的使用,就和使用其父类LinearLayout一样。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <com.example.demos.customviewdemo.ForegroundLinearLayout
  6. android:layout_width="match_parent"
  7. android:layout_height="200dp"
  8. android:layout_centerInParent="true"
  9. android:gravity="center">
  10. <TextView
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:layout_centerInParent="true"
  14. android:text="Hello World!"
  15. android:textColor="@android:color/black"
  16. android:textSize="50dp" />
  17. </com.example.demos.customviewdemo.ForegroundLinearLayout>
  18. </RelativeLayout>

效果:在宽为全屏宽度,高为200dp的布局范围内,绘制完子其子控件TextView后,在上面覆盖了一层浅红色的半透明蒙板。

从上面两个例子可见,继承系统原有的控件来实现自定义View,步骤非常简单,比组合控件简单多了。但是这一节需要对Canvas,paint,Path等绘制方面的知识有一定的了解,且还需要对ViewGroup的中内容的绘制顺序有一定的了解,才能在原生控件的基础上做出想要的效果来。

3.自绘控件

这三种方法中,自绘控件是最复杂的,因为所有的绘制逻辑和流程都需要自己完成。采用自绘控件这种方式时,如果自定义View为最终的叶子控件,那么需要直接继承View;而不过自定义View为容器类控件,则需要直接继承ViewGroup。这里依然针对直接继承View和ViewGroup分别举例进行说明。

3.1、自绘View控件

直接继承View会比上一种实现方复杂一些,这种方法的使用情景下,完全不需要复用系统控件的逻辑。自绘叶子View控件时,最主要工作就是绘制出丰富的内容,这一过程是在重写的onDraw方法中实现的。由于是叶子view,它没有子控件了,所以重写onLayout没有意义。onMeasure的方法可以根据自己的需要来决定是否需要重写,很多情况下,不重写该方法并不影响正常的绘制。

我们用自定义View来绘制一个正方形。

1.首先定义构造方法,以及做一些初始化操作

  1. public class RectView extends View{
  2. //定义画笔
  3. private Paint mPaint = new Paint();
  4. /**
  5. * 实现构造方法
  6. * @param context
  7. */
  8. public RectView(Context context) {
  9. super(context);
  10. init();
  11. }
  12. public RectView(Context context, @Nullable AttributeSet attrs) {
  13. super(context, attrs);
  14. init();
  15. }
  16. public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  17. super(context, attrs, defStyleAttr);
  18. init();
  19. }
  20. private void init() {
  21. mPaint.setColor(Color.BLUE);
  22. }
  23. }

2.重写draw方法,绘制正方形,注意对padding属性进行设置

  1. /**
  2. * 重写draw方法
  3. * @param canvas
  4. */
  5. @Override
  6. protected void onDraw(Canvas canvas) {
  7. super.onDraw(canvas);
  8. //获取各个编剧的padding值
  9. int paddingLeft = getPaddingLeft();
  10. int paddingRight = getPaddingRight();
  11. int paddingTop = getPaddingTop();
  12. int paddingBottom = getPaddingBottom();
  13. //获取绘制的View的宽度
  14. int width = getWidth()-paddingLeft-paddingRight;
  15. //获取绘制的View的高度
  16. int height = getHeight()-paddingTop-paddingBottom;
  17. //绘制View,左上角坐标(0+paddingLeft,0+paddingTop),右下角坐标(width+paddingLeft,height+paddingTop)
  18. canvas.drawRect(0+paddingLeft,0+paddingTop,width+paddingLeft,height+paddingTop,mPaint);
  19. }

3.重写onMeasure方法

在View的源码当中并没有对AT_MOSTEXACTLY两个模式做出区分,也就是说View在wrap_contentmatch_parent两个模式下是完全相同的,都会是match_parent,显然这与我们平时用的View不同,所以我们要重写onMeasure方法。

  1. /**
  2. * 重写onMeasure方法
  3. *
  4. * @param widthMeasureSpec
  5. * @param heightMeasureSpec
  6. */
  7. @Override
  8. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  9. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  10. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  11. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  12. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  13. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  14. //处理wrap_contentde情况
  15. if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
  16. setMeasuredDimension(300, 300);
  17. } else if (widthMode == MeasureSpec.AT_MOST) {
  18. setMeasuredDimension(300, heightSize);
  19. } else if (heightMode == MeasureSpec.AT_MOST) {
  20. setMeasuredDimension(widthSize, 300);
  21. }
  22. }

整个过程大致如下,直接继承View时需要有几点注意:

  1. 1、在onDraw当中对padding属性进行处理。
  2. 2、在onMeasure过程中对wrap_content属性进行处理。
  3. 3、至少要有一个构造方法。

3.2继承ViewGroup

自定义ViewGroup的过程相对复杂一些,因为除了要对自身的大小和位置进行测量之外,还需要对子View的测量参数负责。

 自绘ViewGroup控件,需要直接继承ViewGroup,在该系列第一篇文章中将绘制流程的时候就讲过,onLayout是ViewGroup中的抽象方法,其直接继承者必须实现该方法。所以这里,onLayout方法必须要实现的,如果这里面的方法体为空,那该控件的子view就无法显示了。要想准确测量,onMeasure方法也是要重写的。

实现一个类似于Viewpager的可左右滑动的布局。

1.继承ViewGroup类,定义构造方法,进行一些初始化操作

2.重写onMeasure方法

3.接下来重写`onLayout`方法,对各个子View设置位置。

4.事件分发处理,从onInterceptTouchEvent开始

5.当ViewGroup拦截下用户的横向滑动事件以后,后续的Touch事件将交付给`onTouchEvent`进行处理。

6.在XML代码当中引入自定义View

示例:

  1. public class HorizontaiView extends ViewGroup {
  2. private int lastX;
  3. private int lastY;
  4. private int currentIndex = 0;
  5. private int childWidth = 0;
  6. private Scroller scroller;
  7. private VelocityTracker tracker;
  8. /**
  9. * 1.创建View类,实现构造函数
  10. * 实现构造方法
  11. * @param context
  12. */
  13. public HorizontaiView(Context context) {
  14. super(context);
  15. init(context);
  16. }
  17. public HorizontaiView(Context context, AttributeSet attrs) {
  18. super(context, attrs);
  19. init(context);
  20. }
  21. public HorizontaiView(Context context, AttributeSet attrs, int defStyleAttr) {
  22. super(context, attrs, defStyleAttr);
  23. init(context);
  24. }
  25. private void init(Context context) {
  26. scroller = new Scroller(context);
  27. tracker = VelocityTracker.obtain();
  28. }
  29. /**
  30. * 2、根据自定义View的绘制流程,重写`onMeasure`方法,注意对wrap_content的处理
  31. * 重写onMeasure方法
  32. * @param widthMeasureSpec
  33. * @param heightMeasureSpec
  34. */
  35. @Override
  36. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  37. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  38. //获取宽高的测量模式以及测量值
  39. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  40. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  41. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  42. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  43. //测量所有子View
  44. measureChildren(widthMeasureSpec, heightMeasureSpec);
  45. //如果没有子View,则View大小为0,0
  46. if (getChildCount() == 0) {
  47. setMeasuredDimension(0, 0);
  48. } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
  49. View childOne = getChildAt(0);
  50. int childWidth = childOne.getMeasuredWidth();
  51. int childHeight = childOne.getMeasuredHeight();
  52. //View的宽度=单个子View宽度*子View个数,View的高度=子View高度
  53. setMeasuredDimension(getChildCount() * childWidth, childHeight);
  54. } else if (widthMode == MeasureSpec.AT_MOST) {
  55. View childOne = getChildAt(0);
  56. int childWidth = childOne.getMeasuredWidth();
  57. //View的宽度=单个子View宽度*子View个数,View的高度=xml当中设置的高度
  58. setMeasuredDimension(getChildCount() * childWidth, heightSize);
  59. } else if (heightMode == MeasureSpec.AT_MOST) {
  60. View childOne = getChildAt(0);
  61. int childHeight = childOne.getMeasuredHeight();
  62. //View的宽度=xml当中设置的宽度,View的高度=子View高度
  63. setMeasuredDimension(widthSize, childHeight);
  64. }
  65. }
  66. /**
  67. * 3、接下来重写`onLayout`方法,对各个子View设置位置。
  68. * 设置子View位置
  69. * @param changed
  70. * @param l
  71. * @param t
  72. * @param r
  73. * @param b
  74. */
  75. @Override
  76. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  77. int childCount = getChildCount();
  78. int left = 0;
  79. View child;
  80. for (int i = 0; i < childCount; i++) {
  81. child = getChildAt(i);
  82. if (child.getVisibility() != View.GONE) {
  83. childWidth = child.getMeasuredWidth();
  84. child.layout(left, 0, left + childWidth, child.getMeasuredHeight());
  85. left += childWidth;
  86. }
  87. }
  88. }
  89. /**
  90. * 4、因为我们定义的是ViewGroup,从onInterceptTouchEvent开始。
  91. * 重写onInterceptTouchEvent,对横向滑动事件进行拦截
  92. * @param event
  93. * @return
  94. */
  95. @Override
  96. public boolean onInterceptTouchEvent(MotionEvent event) {
  97. boolean intercrpt = false;
  98. //记录当前点击的坐标
  99. int x = (int) event.getX();
  100. int y = (int) event.getY();
  101. switch (event.getAction()) {
  102. case MotionEvent.ACTION_MOVE:
  103. int deltaX = x - lastX;
  104. int delatY = y - lastY;
  105. //当X轴移动的绝对值大于Y轴移动的绝对值时,表示用户进行了横向滑动,对事件进行拦截
  106. if (Math.abs(deltaX) > Math.abs(delatY)) {
  107. intercrpt = true;
  108. }
  109. break;
  110. }
  111. lastX = x;
  112. lastY = y;
  113. //intercrpt = true表示对事件进行拦截
  114. return intercrpt;
  115. }
  116. /**
  117. * 5、当ViewGroup拦截下用户的横向滑动事件以后,后续的Touch事件将交付给`onTouchEvent`进行处理。
  118. * 重写onTouchEvent方法
  119. * @param event
  120. * @return
  121. */
  122. @Override
  123. public boolean onTouchEvent(MotionEvent event) {
  124. tracker.addMovement(event);
  125. //获取事件坐标(x,y)
  126. int x = (int) event.getX();
  127. int y = (int) event.getY();
  128. switch (event.getAction()) {
  129. case MotionEvent.ACTION_MOVE:
  130. int deltaX = x - lastX;
  131. int delatY = y - lastY;
  132. //scrollBy方法将对我们当前View的位置进行偏移
  133. scrollBy(-deltaX, 0);
  134. break;
  135. //当产生ACTION_UP事件时,也就是我们抬起手指
  136. case MotionEvent.ACTION_UP:
  137. //getScrollX()为在X轴方向发生的便宜,childWidth * currentIndex表示当前View在滑动开始之前的X坐标
  138. //distance存储的就是此次滑动的距离
  139. int distance = getScrollX() - childWidth * currentIndex;
  140. //当本次滑动距离>View宽度的1/2时,切换View
  141. if (Math.abs(distance) > childWidth / 2) {
  142. if (distance > 0) {
  143. currentIndex++;
  144. } else {
  145. currentIndex--;
  146. }
  147. } else {
  148. //获取X轴加速度,units为单位,默认为像素,这里为每秒1000个像素点
  149. tracker.computeCurrentVelocity(1000);
  150. float xV = tracker.getXVelocity();
  151. //当X轴加速度>50时,也就是产生了快速滑动,也会切换View
  152. if (Math.abs(xV) > 50) {
  153. if (xV < 0) {
  154. currentIndex++;
  155. } else {
  156. currentIndex--;
  157. }
  158. }
  159. }
  160. //对currentIndex做出限制其范围为【0,getChildCount() - 1】
  161. currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? getChildCount() - 1 : currentIndex;
  162. //滑动到下一个View
  163. smoothScrollTo(currentIndex * childWidth, 0);
  164. tracker.clear();
  165. break;
  166. }
  167. lastX = x;
  168. lastY = y;
  169. return true;
  170. }
  171. private void smoothScrollTo(int destX, int destY) {
  172. //startScroll方法将产生一系列偏移量,从(getScrollX(), getScrollY()),destX - getScrollX()和destY - getScrollY()为移动的距离
  173. scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), destY - getScrollY(), 1000);
  174. //invalidate方法会重绘View,也就是调用View的onDraw方法,而onDraw又会调用computeScroll()方法
  175. invalidate();
  176. }
  177. //重写computeScroll方法
  178. @Override
  179. public void computeScroll() {
  180. super.computeScroll();
  181. //当scroller.computeScrollOffset()=true时表示滑动没有结束
  182. if (scroller.computeScrollOffset()) {
  183. //调用scrollTo方法进行滑动,滑动到scroller当中计算到的滑动位置
  184. scrollTo(scroller.getCurrX(), scroller.getCurrY());
  185. //没有滑动结束,继续刷新View
  186. postInvalidate();
  187. }
  188. }
  189. }
  1. <com.example.yf.view.HorizontaiView
  2. android:id="@+id/test_layout"
  3. android:layout_width="match_parent"
  4. android:layout_height="400dp">
  5. <ListView
  6. android:id="@+id/list1"
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent">
  9. </ListView>
  10. <ListView
  11. android:id="@+id/list2"
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent">
  14. </ListView>
  15. <ListView
  16. android:id="@+id/list3"
  17. android:layout_width="match_parent"
  18. android:layout_height="match_parent">
  19. </ListView>
  20. </com.example.yf.view.HorizontaiView>

4自定义属性

4.1.自定义属性实现

Android系统的控件以android开头的都是系统自带的属性。为了方便配置自定义View的属性,我们也可以自定义属性值。
Android自定义属性可分为以下几步:

  1. 自定义一个View
  2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
  3. 在布局文件中View使用自定义的属性(注意namespace)
  4. 在View的构造方法中通过TypedArray获取

1.首先在values目录下创建attrs.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="test">
  4. <attr name="text" format="string" />
  5. <attr name="testAttr" format="integer" />
  6. </declare-styleable>
  7. </resources>

2.自定义View类:在java代码中进行设置

  1. public class MyTextView extends View {
  2. private static final String TAG = MyTextView.class.getSimpleName();
  3. //在View的构造方法中通过TypedArray获取
  4. public MyTextView(Context context, AttributeSet attrs) {
  5. super(context, attrs);
  6. TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);
  7. String text = ta.getString(R.styleable.test_testAttr);
  8. int textAttr = ta.getInteger(R.styleable.test_text, -1);
  9. Log.e(TAG, "text = " + text + " , textAttr = " + textAttr);
  10. ta.recycle();
  11. }
  12. }

3.布局文件中使用

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. xmlns:app="http://schemas.android.com/apk/res/com.example.test"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent" >
  6. <com.example.test.MyTextView
  7. android:layout_width="100dp"
  8. android:layout_height="200dp"
  9. app:testAttr="520"
  10. app:text="helloworld" />
  11. </RelativeLayout>

4.2.常用的11种format类型

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="SelfAttr">
  4. <!--1.reference:参考某一资源ID-->
  5. <attr name="background" format="reference" />
  6. <!--2. color:颜色值-->
  7. <attr name = "textColor" format = "color" />
  8. <!--3.boolean:布尔值-->
  9. <attr name = "focusable" format = "boolean" />
  10. <!--4.dimension:尺寸值-->
  11. <attr name = "layout_width" format = "dimension" />
  12. <!--5. float:浮点值-->
  13. <attr name = "fromAlpha" format = "float" />
  14. <!--6.integer:整型值-->
  15. <attr name = "lines" format="integer" />
  16. <!--7.string:字符串-->
  17. <attr name = "text" format = "string" />
  18. <!--8.fraction:百分数-->
  19. <attr name = "pivotX" format = "fraction" />
  20. <!--9.enum:枚举值。属性值只能选择枚举值中的一个-->
  21. <attr name="orientation">
  22. <enum name="horizontal" value="0" />
  23. <enum name="vertical" value="1" />
  24. </attr>
  25. <!--10.flag:位或运算。属性值可以选择其中多个值-->
  26. <attr name="gravity">
  27. <flag name="top" value="0x01" />
  28. <flag name="bottom" value="0x02" />
  29. <flag name="left" value="0x04" />
  30. <flag name="right" value="0x08" />
  31. <flag name="center_vertical" value="0x16" />
  32. ...
  33. </attr>
  34. <!--11.混合类型:属性定义时可以指定多种类型值-->
  35. <attr name = "background_2" format = "reference|color" />
  36. </declare-styleable>
  37. </resources>

示例:

  1. <!--1.reference:参考某一资源ID-->
  2. <ImageView android:background = "@drawable/图片ID"/>
  3. <!--2. color:颜色值-->
  4. <TextView android:textColor = "#00FF00"/>
  5. <!--3.boolean:布尔值-->
  6. <Button android:focusable = "true"/>
  7. <!--4.dimension:尺寸值-->
  8. <Button android:layout_width = "42dp"/>
  9. <!--5. float:浮点值-->
  10. <alpha android:fromAlpha = "1.0"/>
  11. <!--6.integer:整型值-->
  12. <TextView android:lines="1"/>
  13. <!--7.string:字符串-->
  14. <TextView android:text = "我是文本"/>
  15. <!--8.fraction:百分数-->
  16. <rotate android:pivotX = "200%"/>
  17. <!--9.enum:枚举值-->
  18. <LinearLayout
  19. android:orientation = "vertical">
  20. </LinearLayout>
  21. <!--10.flag:位或运算-->
  22. <TextView android:gravity="bottom|left"/>
  23. <!--11.混合类型:属性定义时可以指定多种类型值-->
  24. <ImageView android:background = "@drawable/图片ID" />
  25. <!--或者-->
  26. <ImageView android:background = "#00FF00" />

要完成一些酷炫的自定义View,还需要好好地掌握Canvas,Paint,Path等工具的使用,以及View的绘制流程原理

 

 

https://www.cnblogs.com/andy-songwei/p/10979161.html

https://www.jianshu.com/p/705a6cb6bfee

https://www.jianshu.com/p/af266ff378c6

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/759868
推荐阅读
相关标签
  

闽ICP备14008679号