• Android简易实战教程--第二十七话《自定义View入门案例之开关按钮详细分析》


    转载此博客请注明出处点击打开链接       http://blog.csdn.net/qq_32059827/article/details/52444145

    对于自定义view,可能是一个比较大的瓶颈期。笔者也是如此,就像毛主席说的,抓住主要矛盾,一切都不难。一些大神也声称过自定义view并不难。PS笔者比较实在,是真还没到感觉自定义view不难的水平!

    这一篇文章,将对一个案例做详细的代码分析;即使说是入门的案例,但是掌握起来也不是那么容易;笔者尽量把所有代码标注注释帮助理解,同时,我觉得对初学者入门可能还要一段时间。

    首先简单介绍view的绘制过程以及一些简单的理论知识:

    View的绘制流程(相对性)
        1. mearsue: 测量,final,控制控件的大小
        2. layout: 布局,用来控制自己的布局位置
        3. draw: 绘制,用来控制控件的显示样式

        mearsure --> layout ---> draw。这是系统的绘制调用过程。但是对于开发者而言,暴露的方法是下边这三个方法:1. onMearsure:  2. onLayout:3. onDraw:

     View的行为:
        1. click,longClick,安卓系统没有这些方法,只有ontauch方法,都是封装好的
        1. dispatchTouchEvent():touch分发,android希望用来处理是否分发touch事件
        2. onInterceptTouchEvent():touch拦截,android希望处理是否拦截touch事件
            1. 是否拦截孩子touch
        3. onTouchEvent(): touch处理, anroid希望开发人员封装触摸行为给用户提供交换
        4. setOnTouchListener():暴露给开发者,实现监听的回调

     View的刷新调用顺序: invalidate() ---> draw() ---> onDraw()

    好了,废话了一大堆。也该来点代码  ”解解渴“。先看一下自定义View类是咋个回事:

    package com.itydl.Switch;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    
    public class SwitchView extends View {
    
    	private Bitmap mSwitchBackground;
    	Bitmap mSwitchSlide;
    	private boolean isOpened = true;// 滑块的状态,默认开启
    
    	/** 为行为打标记 **/
    	private final static int STATE_NONE = 0;// 默认状态值,空状态
    	private final static int STATE_DOWN = 1;// 按下状态值
    	private final static int STATE_MOVE = 2;// 移动状态值
    	private final static int STATE_UP = 3;// 点击松开状态值
    	private float curentX;
    
    	private int currentState = STATE_NONE;// 当前状态标记,默认空状态
    	private OnSwitchClickListener mListener;
    
    	public SwitchView(Context context) {
    		this(context, null);
    		// 构造方法,new的时候调用
    	}
    
    	public SwitchView(Context context, AttributeSet attrs) {
    		super(context, attrs);
    		//构造方法,写布局的时候调用
    	}
    
    	/**
    	 * 设置背景的图片
    	 * 
    	 * @param resId
    	 *            设置背景图片的资源id
    	 */
    	public void setSwitchBackground(int resId) {
    		// 背景图片
    		mSwitchBackground = BitmapFactory.decodeResource(getResources(), resId);
    	}
    
    	/**
    	 * 设置滑块的图片
    	 * 
    	 * @param resId
    	 *            设置滑块图片的资源id
    	 */
    	public void setSwitchSlide(int resId) {
    		// 滑块
    		mSwitchSlide = BitmapFactory.decodeResource(getResources(), resId);
    	}
    
    	/**
    	 * 当前控件测量自己的宽高时,调用此方法
    	 */
    	// 要不要绘制控件的大小?
    	@Override
    	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    		// 用于设置自己的大小。在onMearsure里面调用setMeasuredDimension(width,height):
    		// 用来设置(测量)自己的大小
    		if (mSwitchBackground != null) {
    			int width = mSwitchBackground.getWidth();// 获取背景图片的大小,宽度
    			int height = mSwitchBackground.getHeight();// 获取背景图片的大小,高度
    			setMeasuredDimension(width, height);// 设置背景大小。设置【控件自己的宽高】正好等于背景图片的宽高
    		} else {
    			// 系统默认设置的大小(其实是填充父组件大小)
    			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    		}
    	}
    
    	/**
    	 * 当前控件开始绘制时,回调此方法
    	 * 绘制开关的状态
    	 */
    	// 要不要设置控件的样子?绘制
    	@Override
    	protected void onDraw(Canvas canvas) {
    		// 父类空实现,注销下边代码
    		// super.onDraw(canvas);
    		// 绘制背景的显示
    		if (mSwitchBackground != null) {
    			int left = 0;//bitmap相对于控件是贴在一起的,所以值为0
    			int top = 0;
    			/**
    			 * bitmap The bitmap to be drawn 要绘制谁,绘制背景图片 表示相对控件左上角坐标位置: 
    			 * position of the left side of the bitmap being drawn ,绘制的背景图片,相对于控件的相对宽度
    			 * position of the top side of the bitmap being drawn ,绘制的背景图片,相对于控件的相对高度
    			 * paint used to draw the bitmap (may be null) 画笔为null
    			 */
    			//绘制背景的显示,背景图片绘制出来。
    			canvas.drawBitmap(mSwitchBackground, left, top, null);//在控件上画图片
    		}
    
    		if (mSwitchSlide == null) {
    			return;
    		}
    
    		// 获取控件滑块的中点坐标
    		int slideWidth = mSwitchSlide.getWidth();// 滑块的宽度
    		// 获取背景的宽度坐标
    		int switchBackWidth = mSwitchBackground.getWidth();// 背景宽度
    
    		switch (currentState) {//onDraw()不知道具体什么状态,因此用使用状态标记
    		case STATE_DOWN:
    		case STATE_MOVE://移动状态和按下状态是一样的
    
    			// 是否是按下
    			if (!isOpened) {
    				// 滑块处于关闭状态
    				// 判断左右,设置状态
    				if (curentX < slideWidth / 2f) {//点击左侧
    					// 点击的滑块左侧,不变化。但是要有一次绘制当前状态位置,绘制在左侧
    					canvas.drawBitmap(mSwitchSlide, 0, 0, null);
    				} else {
    					// 点击滑块的右侧,滑块的中线和按下鼠标的x坐标对齐。计算滑块左侧的坐标值
    					float left = curentX - slideWidth / 2f;
    					// 获取最大的left值(滑块往右滑动,有临界值,否则会滑出背景)
    					float maxLeft = switchBackWidth - slideWidth;
    					// 判断是否越界
    					if (left > maxLeft) {//如果越界
    						left = maxLeft;// 设置滑块左侧坐标为最大位置
    					}
    					// 绘制这个位置
    					canvas.drawBitmap(mSwitchSlide, left, 0, null);
    				}
    
    			} else {
    				// 滑块处于打开状态
    				// 获取滑块的中线坐标
    				float middleX = switchBackWidth - slideWidth / 2f;
    				if (curentX > middleX) {
    					// 点击的位置在滑块的中线右侧,无反应,设置默认
    					canvas.drawBitmap(mSwitchSlide, switchBackWidth
    							- slideWidth, 0, null);
    				} else {
    					// 点击的位置在滑块的中线左侧,滑块中线等于当前点击的x值位置
    					// 当前滑块左边的坐标
    					float left = curentX - slideWidth / 2f;
    					if (left < 0) {// 点击左边不要越界
    						left = 0;
    					}
    					// 点击位置没有越界,滑块中线等于当前点击的x值位置
    					canvas.drawBitmap(mSwitchSlide, left, 0, null);
    
    				}
    
    			}
    			break;
    
    		case STATE_UP:
    			if (!isOpened) {
    				// 关闭状态
    				canvas.drawBitmap(mSwitchSlide, 0, 0, null);
    
    				// System.out.println("关闭");
    			} else {
    
    				canvas.drawBitmap(mSwitchSlide, switchBackWidth - slideWidth,
    						0, null);
    
    				// System.out.println("打开");
    			}
    
    			break;
    		case STATE_NONE:// 设置默认状态
    			// 绘制滑块,判断状态标记,来设置滑块的默认状态位置
    			if (!isOpened) {// 关闭
    				// 先写死为(0,0)
    				canvas.drawBitmap(mSwitchSlide, 0, 0, null);
    			} else {
    				// 打开状态
    				canvas.drawBitmap(mSwitchSlide, switchBackWidth - slideWidth,
    						0, null);
    			}
    
    			break;
    
    		default:
    			break;
    		}
    
    	}
    
    	/**
    	 * 用户触摸调用
    	 */
    	@Override
    	public boolean onTouchEvent(MotionEvent event) {
    		// 重写onTouchEvent方法就养成写出三大状态的习惯
    		switch (event.getAction()) {
    		case MotionEvent.ACTION_DOWN:
    			// 状态改变,行为标记改变
    			currentState = STATE_DOWN;
    			// 按下鼠标的相对横坐标:
    			curentX = event.getX();
    
    			//获取到最新坐标,需要绘制一下最新坐标。但是在onTouchEvent方法中是获取不到canvas对象的。因此使用invalidate();
    			
    			invalidate();// View的刷新调用顺序: invalidate() ---> draw() ---> onDraw()
    
    			break;
    		case MotionEvent.ACTION_MOVE:// 绘制过程和点击绘制过程一样,滑块中点跟着当前鼠标x坐标变化
    			currentState = STATE_MOVE;
    			curentX = event.getX();
    
    			invalidate();// View的刷新调用顺序: invalidate() ---> draw() ---> onDraw().---->主线程调用
    			//postInvalidate();//触发刷新----->子线程调用的
    			break;
    		case MotionEvent.ACTION_UP:
    			currentState = STATE_UP;
    			curentX = event.getX();
    			// 根据滑动松开位置情况,设置跳转到打开还是关闭状态标记
    			// 获取背景中点
    			int switchBackWidth = mSwitchBackground.getWidth();
    			if (curentX < switchBackWidth / 2f && isOpened) {// 节省资源,本来关闭的,不再触发关闭打印。&& isOpened表示是否当前是打开状态。
    				// 状态值设置为关闭状态
    				isOpened = false;
    				if (mListener != null) {
    					
    					//调用接口方法,实际调用实现类方法
    					mListener.onSwitchChanged(isOpened);
    				}
    			} else if (curentX >= switchBackWidth / 2f && !isOpened) {// && !isOpened表示当前是否是关闭状态
    				isOpened = true;
    				if (mListener != null) {
    
    					//调用接口方法,实际调用实现类方法
    					mListener.onSwitchChanged(isOpened);
    					
    				}
    			}
    
    			invalidate();// View的刷新调用顺序: invalidate() ---> draw() ---> onDraw()
    			break;
    
    		default:
    			break;
    		}
    		// 消费触摸事件,不使用父类的
    		return true;
    	}
    
    	/**
    	 * 调用此方法,设置开关状态的点击事件
    	 * 
    	 * @param listener
    	 */
    	public void setOnSwitchClickListener(OnSwitchClickListener listener) {
    		this.mListener = listener;
    	}
    
    	/**
    	 * 开关状态监听
    	 * 
    	 * @author lenovo
    	 * 
    	 */
    	public interface OnSwitchClickListener {
    		/**
    		 * 接口回调,实现此方法,根据开关打开关闭做相应的事件。参数:打开true;关闭false
    		 */
    		void onSwitchChanged(boolean isOpend);
    	}
    
    }
    

    自定义了view,就引入这个view——

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <com.itydl.Switch.SwitchView
            android:id="@+id/swit_switchview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
        </com.itydl.Switch.SwitchView>
    
    </RelativeLayout>


    既然设置了回调,就看看manactivity代码:

    package com.itydl.Switch;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.Toast;
    
    import com.itydl.Switch.SwitchView.OnSwitchClickListener;
    
    public class MainActivity extends Activity {
    
        private SwitchView mSwitchView;
    
    	@Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initView();
        }
    
    	private void initView() {
    		// TODO Auto-generated method stub
    		setContentView(R.layout.activity_main);
    		//获取自定义view的实例
    		mSwitchView = (SwitchView) findViewById(R.id.swit_switchview);
    		//设置背景图片,传递资源到自定义view
    		mSwitchView.setSwitchBackground(R.drawable.switch_background);
    		//设置滑块资源图片,把滑块的资源图片id传递到自定义view
    		mSwitchView.setSwitchSlide(R.drawable.slide_button_background);
    		
    		//根据开关打开关闭,设置点击事件
    		mSwitchView.setOnSwitchClickListener(new OnSwitchClickListener() {
    			
    			@Override
    			public void onSwitchChanged(boolean isOpend) {
    				// TODO Auto-generated method stub
    				Toast.makeText(getApplicationContext(), isOpend? "打开":"关闭", 0).show();
    			}
    		});
    	}
        
    }
    
    运行结果如下:


    欢迎关注本博客点击打开链接  http://blog.csdn.net/qq_32059827,每天花上5分钟,阅读一篇有趣的安卓小文哦

  • 相关阅读:
    MongoDB 数据类型
    linux 查看网卡流量:nload
    Linux 抓包工具:tcpdump
    python 合并列表 从大到小排序
    MongoDB的客户端管理工具--nosqlbooster 连接MongoDB服务器
    MongoDB 用户管理
    MongoDB 数据库操作
    MongoDB与关系型数据库 区别
    MongoDB 新建数据库和集合 查询集合
    POJ 1491
  • 原文地址:https://www.cnblogs.com/wanghang/p/6299597.html
Copyright © 2020-2023  润新知