自定义view:
一般系统控件满足不了我们的需求时,这时候要考虑要使用自定义控件了。由于自定义控件也是一个对象,把这个自定义控件想象成一个简单的创建对象
的过程就可以了,例如下面我们要创建一个Person对象,
Person p = new Person("张三","22","男");
或者
Person p = new Person();
p.setName("张三");
p.setAge("22");
p.setGender("男");
首先肯定要new出来这个对象,其次要给对象必要的初始化操作。完成这两部基本一个对象就可以使用了。那么自定义对象也是
一样的,首先要new出来,其次做必要的设置.于是步骤基本如下:
调用构造new 出对象:我们自定义控件一般在布局文件中引用,那么控件的创建会由系统来替我们完成。
初始化设置:
测量出view对象的大小:对应的方法为onMeasure
确定view的位置: 对应的方法为onLayout ,view的位置是由父view来决定的。
绘制View的内容: 对应的方法为onDraw
那么一个view显示在屏幕上 依次经过的过程大致就是:
1:首先创建出来MyView(Context context, AttributeSet attrs)。
2:调用测量方法测量 view的大小(如果需要onMeasure)
3:定位view的位置(如果需要onLayout)
4:绘制view的内容(如果需要onDraw)
5:绘制内容时一般结合手势事件等做相应业务处理。(如果需要onTouchEvent)
-------------------------------------------------------------------------------------------
一、继承 View类,并复写构造方法。构造方法的作用 说明如下。
//在代码中创建此自定义控件时 调用此方法。
public MyView(Context context){
super(context);
}
/**当我们在布局文件中使用系统控件时,系统会给我们自动创建,调用的就是此构造方法。由此,当我们自定义控件时,
* 必须复写此构造方法,否则运行时会报错。
*/
public MyView(Context context,AttributeSet attrs){
super(context);
//
}
// 和第二个构造方法一样,只不过多了一个样式参数,因此此构造用来给设置样式(如果需要的话)。
public MyView(Context context,AttributeSet attrs,int Style){
super(context);
}
二、测量大小:
onMeasure:这个方法是测量大小的回调方法
//从名字可以看出是“设置测量数值”。
//设置view的宽高,单位都是像素值。
setMeasuredDimension()
@Override
/**
* 测量尺寸时的回调方法
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置当前view的大小
* width :view的宽度
* height :view的高度 (单位:像素)
*/
setMeasuredDimension(backgroundBitmap.getWidth(),backgroundBitmap.getHeight());
}
三、确定位置:onLayout:自定义View的时候作用不大。
四、绘制内容onTouchEvent 和 onDraw:
onDraw(Canvas canvas){//画线,圆,背景图片,等等。。。。。
}
onTouchEvent(){
//对触摸事件解析,并根据业务做相应处理
}
五、关于控件的移动。
1、改变坐标(横坐标或者纵坐标),并通过刷新坐标值来达到移动的目的。例如onDraw(Canvas canvas){
canvas.drawBitmap(slideBtn, left, top, paint);//left 距离原点横坐标,top纵坐标
//根据业务刷新
invalidate();
}
自定义ViewGroup:
1、继承viewGroup:这时会强制让复写构造方法,这里复写带2个参数的public MyLayout(Context context, AttributeSet attrs) {}构造
2.重写onMeasure:在自定义view时,我们一般调用setMeasuredDimension来设置我们view的大小。但作为一个viewgroup,只计算自己的大小还不够,还
得计算子View的大小。那么怎么怎么计算子view大小呢?:一次获取子view,每个子view测量即可。
/**
* @param widthMeasureSpec
* @param heightMeasureSpec
*
* 1、View的布局大小由父View和子View共同决定。
*
* 2、View的测量最终调用的是View的setMeasuredDimension方法,所以我们可以通过
* setMeasuredDimension设定死值来完成测量。但是一个好的自定义View应该会根据子视图的
* measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小.
* 3、widthMeasureSpec heightMeasureSpec 这两个参数表示父元素传入的
* 4、测量从根view开始依次遍历整个view树结构。每个view都会经过测量。
* 5、我们这里先测量子view,最后测量当前viewgroup。
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//先测量所有的子view这里获取父viewgroup提供的大小 ,测试运行结果例如:viewgroup:1073742524-1073742958
System.out.println("viewgroup:"+widthMeasureSpec+"-"+heightMeasureSpec);
int sizeW = MeasureSpec.getSize(widthMeasureSpec);
int sizeH = MeasureSpec.getSize(heightMeasureSpec);
//
int modeW = MeasureSpec.getMode(widthMeasureSpec);
int modeH = MeasureSpec.getMode(heightMeasureSpec);
//测试运行结果例如:getSize:700-modeW:1073741824
System.out.println("getSize:"+sizeW+"-modeW:"+modeW);
//一、因此 到这里基本上可以理解到 MeasureSpec.getSize(widthMeasureSpec); 实际上得到的是当前view的大小。
//二、由于是viewgroup,当然也是可供分配的空间大小.如果其下的子view超过这个值,就摆放不下了。
for(int i=0;i<this.getChildCount();i++){
View child = this.getChildAt(i);
int tempSetMode = 0;
switch (modeW) {
case MeasureSpec.AT_MOST:
tempSetMode = MeasureSpec.AT_MOST;
System.out.println("MeasureSpec.AT_MOST");
break;
case MeasureSpec.UNSPECIFIED:
tempSetMode = MeasureSpec.UNSPECIFIED;
System.out.println("MeasureSpec.UNSPECIFIED");
break;
case MeasureSpec.EXACTLY:
tempSetMode = MeasureSpec.EXACTLY;
System.out.println("MeasureSpec.EXACTLY");
break;
}
//在测量的时候有三种规则如下:
//MeasureSpec.UNSPECIFIED://表示view想要多大就是多大。
//MeasureSpec.AT_MOST://表示view想要的大小不能超过某个值。( wrap)
//MeasureSpec.EXACTLY://表示view就是只能是这么大 ( match_fill 固定大小(100dp))
//一、指定固定大小来测量
int myWidth = MeasureSpec.makeMeasureSpec(100,tempSetMode);//这里的参数表示当前view想要按照这种模式测量。
int myHeight = MeasureSpec.makeMeasureSpec(100,tempSetMode);
//child.measure(myWidth, myHeight);
//二、指定固定大小来测量
//measureChild(child, myWidth, myHeight);
//三、将父viewgroup的大小作为参数来测量子view的大小。我理解意思实际上就是 设置子view的大小为父元素的大小。
//运用场景:例如要定义一个类似viewpager效果,那么在测量子view时可以这样设置。
//child.measure(widthMeasureSpec, heightMeasureSpec);
//四、子view随意大小(根据布局中的设置来)
child.measure(MeasureSpec.UNSPECIFIED,MeasureSpec.UNSPECIFIED);
//final:这集中子view的测量方式都可以,可以根绝业务需要来变动。总结起来就2个:
//child.measure(xxx, xxx);
//measureChild(child, xxx, xxx);
}
//这句意思是调用父类的来测量,如果不测量,那么下面必须调用setMeasuredDimension测量,否则报错。
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//我们可以根据业务需要来指定当前viewgroup大小.如果不指定 当然就是setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);了
//setMeasuredDimension(480, 800);
}
3、重写onLayout()方法:为每一个子View确定位置。自定义ViewGroup的主要业务在与onLayout。就是是怎么排版当前布局。
/**
* 若为true说明布局发生了变化。l,t,r,b :当前viewgroup在其父view中的位置(基本用不到)。
* 定位子view 的位置
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for(int i =0;i<this.getChildCount();i++){
View view = getChildAt(i);
//一、getWidth,getHeight :得到当前控件实际的大小,只有当onlayout方法执行完后才确定这个大小。
//二、getMeasuredWidth,getMeasuredHeight :得到当前控件的测量的大小,当onmeasure方法执行完后就可以得到。
//三、从源码得知getWidth是mLeft-mRight后得到的,而mLeft,mRight是在onLayout执行后确定的。因此,getWidth是在onLayout执行后才确定。
// 同样getMeasuredWidth从源码得知是一个&运算后的结果(mMeasuredWidth&MEASURED_SIZE_MASK),而mMeasuredWidth是在onMeasure
// 执行后确定的,因此getMeasuredWidth在onMeasure执行后就可以有值。
//四、一般情况下getWidth和getMeasureWidth是一样的,getMeasureWidth是当前view自身想要多大,而getWidth是父view给指定的大小
System.out.println("viewgroup:getWidth:"+getWidth()+
"getHeight:"+getHeight()+
"getMeasureW:"+getMeasuredWidth()+
"getMeasureH:"+getMeasuredHeight()
);
System.out.println("view:getWidth:"+view.getWidth()+
"getHeight:"+view.getHeight()+
"getMeasureW:"+view.getMeasuredWidth()+
"getMeasureH:"+view.getMeasuredHeight()
);
//根据业务需要 确定控件的位置
//父view会根据子view的需求和自身的情况来确定 子view的位置。
view.layout(i*30, i*30, i*30+view.getMeasuredWidth(), i*30+view.getMeasuredHeight());
}
}
4、重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view(根据业务需要).
// 手势识别工具类
GestureDetector g ;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
//g.onTouchEvent(event);
return true;
}
OnGestureListener listener = new OnGestureListener() {
/**
* 当单个点击抬起的时候触发这个方法。比如按下了好几个手指,只要有一个手指抬起来就会触发这个一个方法。
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
/**
* 当有手指点到屏幕上的时候 会回调这个方法
*/
@Override
public void onShowPress(MotionEvent e) {
// TODO Auto-generated method stub
}
/**
* 当手指在屏幕上划动的时候 回调这个方法。
* e1:
* e2:
* distanceX:
* distanceY:
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
//重要点:
//1、将当前view移动到某个点上。这个点由x,y制定。
//2、
//MyLayout.this.scrollTo(x, y);
//1、scrollBy移动的是viewgroup里面的子元素
//2、distanceX/distanceY: x/y方向上移动的距离
//3、distanceX/distanceY: 手指向左移动:为正值 手指向右移动:为负值
//4、scrollBy---调用的是-->scrollTo(mScrollX + x, mScrollY + y);
MyLayout.this.scrollBy((int)distanceX, (int)distanceY);//x,y方向上都移动
MyLayout.this.scrollBy((int)distanceX, 0);//只有 x水平方向移动.
MyLayout.this.scrollBy(0, (int)distanceY);//只有 y竖直方向移动。
return false;
}
/**
* 在屏幕上长按的时候回调
*/
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
/**
* 当快速划动的时候触发这个方法。
* velocityX>0 :快速向右滑动
* velocityX<0 :快速向左滑动
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
@Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
};
int startX;
int endX;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
gesture.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
endX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
endX = (int) event.getX();
break;
case MotionEvent.ACTION_UP:
滑动时:view向右滑动 则为正值,view向左滑动则为负值。
int directionAndDistance = endX-startX ;
更多关于自定义viewgroup使用的Draghelper,请参考:
Android ViewDragHelper完全解析 自定义ViewGroup神器