Android学习笔记17-自定义控件
来源:互联网 发布:帝国cms快速仿站 编辑:程序博客网 时间:2024/05/21 08:49
1、View绘制的基本概念:
我们所有的控件都是继承View这个类的,View的创建主要有3个步骤 measure(测量设置布局的大小) -> layout(设置布局的位置) -> draw(绘制)在这个3个步骤中系统对应有一些回调函数,我们可以在这个回调函数中做我们自己的事。 onMeasure -> onLayout -> onDraw 我们在layout布局中加载我们自己定义的控件需要注意: a.必须要指定width 和height,因为你不指定就通不过编译,虽然你设置了也没用。 b.自定义的布局一定要加完整的路径,例如:
<com.example.myswitch.MySwitch android:id="@+id/ms_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" />
注意:我们在布局预览中也可看到我们定义的布局哦。
2、自定义view步骤:
a. 写类继承View b. 重写onDraw, 进行绘制 c. 重新onMeasure,修改尺寸 d. 在xml布局文件中配置简单的代码演示:(简单的画个矩形)
public class MySwitch extends View { private Paint mPaint;//定义个全局的画笔 //系统调用的,这个构造在加载我们xml文件会调用,所以一定要重写这个构造 public MySwitch(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } //系统调用的,这个构造在加载我们xml文件会调用,所以一定要重写这个构造 public MySwitch(Context context, AttributeSet attrs) { super(context, attrs); initView(); } //这个是我们程序员调用的,如果你不会手动去new这个布局,也可以不用重写这个构造。 public MySwitch(Context context) { super(context); initView(); } //我们不用每次在draw的时候去new 画笔,浪费内存,所以定义个全局的变量 private void initView() { // 初始化画笔 mPaint = new Paint(); mPaint.setColor(Color.RED);// 画笔颜色 } // 设置尺寸回调 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec);//查看源码,也是调用setMeasuredDimension // 将当前控件宽高设置为100x100,就类似画布设置100*100,你draw的矩形比这个大也没用 setMeasuredDimension(100, 100); } // measure->layout->draw // onMeasure->onLayout->onDraw @Override protected void onDraw(Canvas canvas) { // 绘制200x200的矩形 canvas.drawRect(0, 0, 200, 200, mPaint); System.out.println("onDraw"); } }
这样我们一个简单的自定义View就实现了。
3、自定义一个开关按钮:
在上面的onMeasure方法中我们直接设置了 100*100 可能会导致布局里面的东西显示不全我们就需要动态的设置宽高了 setMeasuredDimension(mBitmapBg.getWidth(), mBitmapBg.getHeight());// 依据背景图片来确定控件大小代码演示:
public class MySwitch extends View { private Paint mPaint; private Bitmap mBitmapBg; private Bitmap mBitmapSlide; private int MAX_LEFT;// 滑块最大左边距 private int mSlideLeft;// 当前左边距 private boolean isOpen;// 当前开关状态 public MySwitch(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } public MySwitch(Context context, AttributeSet attrs) { super(context, attrs); initView(); // 加载自定义滑块图片 int slideId = attrs.getAttributeResourceValue(NAMESPACE, "slide", -1); if (slideId > 0) { mBitmapSlide = BitmapFactory.decodeResource(getResources(), slideId); } } public MySwitch(Context context) { super(context); initView(); } private void initView() { // 初始化画笔 mPaint = new Paint(); mPaint.setColor(Color.RED);// 画笔颜色 // 初始化背景bitmap mBitmapBg = BitmapFactory.decodeResource(getResources(),R.drawable.switch_background); // 初始化滑块bitmap mBitmapSlide = BitmapFactory.decodeResource(getResources(),R.drawable.slide_button); MAX_LEFT = mBitmapBg.getWidth() - mBitmapSlide.getWidth(); this.setOnClickListener(new OnClickListener() {//在整个view设置点击侦听 @Override public void onClick(View v) { if (isClick) {//解决点击和滑动的冲突 if (isOpen) { isOpen = false;// 关闭开关 mSlideLeft = 0; } else { isOpen = true;// 打开开关 mSlideLeft = MAX_LEFT; } invalidate();// view重绘的方法, 刷新view, 重新调用onDraw方法 if (mListener != null) {// 回调当前开关状态 mListener.onCheckChanged(MySwitch.this, isOpen); } } } }); } int startX = 0; int moveX = 0;// 位移距离 boolean isClick;// 标记当前是触摸还是单击事件 @Override public boolean onTouchEvent(MotionEvent event) {//重写方法,实现滑动切换 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 1. 记录起始点x坐标 startX = (int) event.getX();// 获取相对于当前控件的x坐标 break; case MotionEvent.ACTION_MOVE: int endX = (int) event.getX();// 2. 记录移动后的x坐标 int dx = endX - startX;// 3. 记录x偏移量 mSlideLeft += dx;// 4. 根据偏移量,更新mSlideLeft moveX += Math.abs(dx);// 向左向右移动都要统计下来, 所以要用dx绝对值 if (mSlideLeft < 0) {// 避免滑块超出边界 mSlideLeft = 0; } if (mSlideLeft > MAX_LEFT) {// 避免滑块超出边界 mSlideLeft = MAX_LEFT; } invalidate();// 5. 刷新界面 startX = (int) event.getX();// 6. 重新初始化起始点坐标 break; case MotionEvent.ACTION_UP: if (moveX < 5) {// 根据位移判断是单击事件还是移动事件 isClick = true;// 单击事件,小于5个像素认为就是点击 } else { isClick = false;// 移动事件,大于5个像素就认为是滑动 } moveX = 0;// 初始化移动的总距离,不然下次会出错 if (!isClick) {//判断是滑动了 if (mSlideLeft < MAX_LEFT / 2) {// 根据当前位置, 切换开关状态 mSlideLeft = 0;// 关闭开关 isOpen = false; } else { mSlideLeft = MAX_LEFT;// 打开开关 isOpen = true; } invalidate(); if (mListener != null) {// 回调当前开关状态 mListener.onCheckChanged(MySwitch.this, isOpen); } } break; } return super.onTouchEvent(event);//返回true就消耗了事件,这个是为了解决滑动和点击事件冲突 } @Override// 设置尺寸回调 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(mBitmapBg.getWidth(), mBitmapBg.getHeight());// 依据背景图片来确定控件大小 } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmapBg, 0, 0, null);// 绘制背景图片 canvas.drawBitmap(mBitmapSlide, mSlideLeft, 0, null);// 绘制滑块图片 } private OnCheckChangeListener mListener; // 设置开关状态监听 public void setOnCheckChangeListener(OnCheckChangeListener listener) { mListener = listener; } //监听开关状态的回调接口 public interface OnCheckChangeListener { public void onCheckChanged(View view, boolean isChecked); } }
注意: invalidate()方法是让view失效,如果view是可见的状态,就会立即调用ondraw方法。 在解决点击和滑动事件冲突的时候,我们是通过滑动的距离来判断的。
4、设置自定义View的属性:
例如我们可以直接在xml中设置开关的属性来控制第一次显示的是开还是关,图片的样式。步骤: a.在res-values的attr.xml文件输入,没有就新建一个:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MySwitch"> <attr name="slide" format="reference" />//滑块的属性 <attr name="checked" format="boolean" />//开关的状态 </declare-styleable> </resources>
b.在xml中使用:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <!-- 将android替换为我们的包名--> xmlns:mynamespace="http://schemas.android.com/apk/res/com.example.myswitch" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.mynamespace.myswitch.MySwitch android:id="@+id/ms_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" mynamespace:checked="true" mynamespace:slide="@drawable/slide_button" /> </RelativeLayout>
注意:使用我们自定义的属性没有代码提示功能 c.在我们定义的View修改下代码:
加一个全局变量 private static final String NAMESPACE = "http://schemas.android.com/apk/res/com.example.myswitch"; 修改下构造函数,只需要改这个系统调用的就行 public MySwitch(Context context, AttributeSet attrs) { super(context, attrs); initView(); // 获取属性值 isOpen = attrs.getAttributeBooleanValue(NAMESPACE, "checked", false); // 加载自定义滑块图片 int slideId = attrs.getAttributeResourceValue(NAMESPACE, "slide", -1); if (slideId > 0) {//如果指定了滑块的背景图就加载 mBitmapSlide = BitmapFactory.decodeResource(getResources(), slideId); } if (isOpen) { mSlideLeft = MAX_LEFT; } else { mSlideLeft = 0; } invalidate();//刷新下界面 }
这样我们就可以在xml文件中直接设置开关的属性啦。
比较全的开源自定义控件
https://github.com/Trinea/android-open-project/
https://github.com/lightSky/Awesome-MaterialDesign
阅读全文
0 0
- Android学习笔记17-自定义控件
- 【学习笔记】Android自定义控件
- android自定义控件学习笔记
- Android 自定义控件学习笔记
- Android-自定义控件学习笔记
- android学习笔记之自定义控件
- [Android学习笔记]自定义控件的使用
- Android学习笔记:自定义控件篇
- Android学习笔记之自定义控件--标题栏
- android学习笔记--创建自定义控件
- Android学习笔记三—自定义控件
- 学习笔记:自定义控件
- android自定义控件笔记
- android自定义控件笔记
- android 自定义控件学习
- Android自定义控件学习
- android自定义控件--学习
- android学习笔记(二)——自定义控件
- Python3.x安装numpy和matplotlib的问题
- 2017 暑假艾教集训 day5
- Hibernate多对多操作
- python SyntaxError: non-default argument follows default argument
- HDU.2149 Public Sale (博弈论 巴什博弈)
- Android学习笔记17-自定义控件
- 错误记录——Invalid field value for field "position".
- 获取数据库连接工具类
- 2017百度之星资格赛—1003度度熊与邪恶大魔王
- SQL语句基础模板
- HDU.2147 kiki's game (博弈论 PN分析)
- 简述47种Shader Map的渲染原理与制作方法
- 多边形重心问题 java
- Java异常处理