Android⾃定义View⽰例
⼀、继承View复写onDraw⽅法
新建Paint对象⽤于绘制⾃定义图像
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
复写onDraw⽅法(注意⼿动实现padding属性,部分代码)
protected void onDraw(Canvas canvas) {
final int paddingLeft = getPaddingLeft(); //使padding属性⽣效
//在计算宽⾼时,考虑padding
int width = getWidth()-paddingLeft-paddingRight;
//绘制⾃定义图形
canvas.drawCircle(paddingLeft+width/2,+paddingTop+height/2,radius,mPaint);
}
复写onMeasure⽅法,以实现wrap_content
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获得Spec模式
int widthSpecMode = Mode(widthMeasureSpec);
//获得spec宽
int widthSpecSize = Size(widthMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
//复写实现wrap_content,赋予默认值
setMeasuredDimension(200,200);
android layout布局}
//多情况判断
}
以上已粗略完成⼀个简单的⾃定义View,为了使⽤更为⽅便,为⾃定义View添加⾃定义属性
1,在values⽬录下新建l⽂件,⽤于定义⾃定义属性
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<declare-styleable name="CircleView">
<attr name="circle_color" format="color"/>
</declare-styleable>
</resources>
2,在⾃定义View的构造⽅法中,加载⾃定义属性(部分代码)
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CircleView);
//获得⾃定义属性集合,解析属性设置默认值,最后实现资源
mColor = a.getColor(R.styleable.CircleView_circle_color,Color.GREEN);
//获得并使⽤资源,并设置默认值
//实现资源
}
注:1、为了⾃定义属性保证⽣效,在两参数构造⽅法中调⽤三参数构造⽅法。
2、在布局使⽤⾃定义属性时,应使⽤新的命名空间。
⼆、继承ViewGroup派⽣特殊的Layout
⽤于实现⾃定义的布局,需要合适地处理ViewGroup的测量、布局这两个过程,并同时处理⼦元素的测量和布局过程。在此编写⼀⽗布局左右滑动,⼦元素上下滑动的⾃定义布局。以下分段分析代码。
1、复写onInteceptTouchEvent⽅法,事件分发⽅法,⽤于解决滑动冲突的问题。
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
//获得滑动坐标
int x = (X();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
//优化滑动体验
if (!mScroller.isFinished()) {
//此处为滑动结束后,若滑动动画未结束下⼀次事件仍由⽗容 //器拦截,但实际效果不佳,快速切换时误操作频繁
//取消拦截可减少,但会有快速切换仍会有些许迟滞感
mScroller.abortAnimation();
//intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
//记录滑动距离
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
//⽔平滑动距离⼤于垂直时,认为是⽔平滑动事件,⽗容器拦截
if (Math.abs(deltaX) > Math.abs(deltaY)*2) {
intercepted = true;
} else intercepted = false;
break;
}
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
//记录坐标
mLastX = x;
mLastY = y;
mLastYIntercept = y;
mLastXIntercept = x;
return intercepted;
}
2、复写onTouchEvent⽅法,负责处理⽗容器的点击事件,在移动(ACTION_MOVE)时,通过scrollBy⽅法动态移动布局。在操作结束时(ACTION_UP),判断当前的位置已确定是否移动画⾯,以避免⼦元素滑动⾄⼀半的情形。
smoothScroll⽅法为⾃定义弹性滑动⽅法,⽤Scroller实现。
public boolean onTouchEvent(MotionEvent event) {
//跟踪滑动速度
mVelocityTracker.addMovement(event);
int x = (int) X();
int y = (int) Y();
switch (Action()){
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()){
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
//滑动效果
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy(-deltaX,0);
break;
case MotionEvent.ACTION_UP:{
/
/记录滑动距离
int scrollX = getScrollX();
//设置速度事件间隔
mVelocityTrackerputeCurrentVelocity(1000);
float xVelocity = XVelocity();
//⼤于五⼗时,认为⼜滑过⼀个⼦项
if (Math.abs(xVelocity) >= 50){
mChildIndex = xVelocity > 0 ? mChildIndex-1:mChildIndex+1;
}else{
//否则计算得到划过⼦项个数
mChildIndex = (scrollX + mChildWidth / 2)/mChildWidth;
}
//最终值⼤于0⼩于⼦项总数
mChildIndex = Math.max(0,Math.min(mChildIndex,mChildrenSize-1));
//计算⾃动滑动距离,缓慢滑动
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx,0);
mVelocityTracker.clear();
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
3、复写onMeasure⽅法,实现⽗布局以及⼦元素的measure过程,主要逻辑为判断SpecMode类型,分情况计算⽗布局的宽,⾼。⼦元素通过measureChildren⽅法完成measure过程。
此处有待改进的地⽅:没有考虑padding以及margin属性的作⽤,⽽⽆⼦元素时也不应将⾼宽直接赋值为0,应进⼀步判断进⾏赋值。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = 0;
int measureHeight = 0;
final int childCount = getChildCount();
//执⾏⼦元素的Measure⽅法
measureChildren(widthMeasureSpec,heightMeasureSpec);
int widthSpecSize = Size(widthMeasureSpec);
int heightSpecSize = Size(heightMeasureSpec);
int widthSpecMode = Mode(widthMeasureSpec);
int heightSpecMode = Mode(heightMeasureSpec);
//⽆⼦项,⾼宽为0
if (childCount == 0){
setMeasuredDimension(0,0);
}//以下多次判断,由⽗布局的SpecMode,计算得⽗布局的⾼宽并设置
else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = Width()*childCount;
measureHeight = Height();
setMeasuredDimension(measureWidth,measureHeight);
}else if (heightSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureHeight = MeasuredHeight();
setMeasuredDimension(widthSpecSize,measureHeight);
}else if (widthSpecMode == MeasureSpec.AT_MOST){
final View childView = getChildAt(0);
measureWidth = Width()*childCount;
setMeasuredDimension(measureWidth,heightSpecSize);
}
}
4、复写onLayout⽅法,⽤于完成⽗布局的layout过程,即是遍历所有⼦元素,完成⼦元素的layout过程。
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < mChildrenSize ; i++){
final View childView = getChildAt(i);
//判断是否可见
if (Visibility() != View.GONE){
//执⾏⼦元素Layout
final int childWidth = MeasuredWidth();
mChildWidth = childWidth;
childView.layout(childLeft,0,childLeft+MeasuredHeight());
childLeft += childWidth;
}
}
}
以上基本完成了⼀个简单的⾃定义View,还有⼀些细节问题,如Scroller、VelocityTracker相关⽅法的使⽤等。
总结⼀下,实现继承Viewgroup的⾃定义layout,只需分别⼿动实现Measure、Layout、onTouchEvent⽅法,完成⽗容器以及⼦元素的构建过程即可。为了解决滑动冲突的问题,也只需复写onInterceptTouchEvent⽅法,实现⾃定义的事件分发逻辑。
其中,Measure过程调⽤measureChildren⽅法完成⼦元素的测量过程,⽗布局则根据SpecMode具体计算宽⾼。
Layout过程遍历⼦元素,调⽤⼦元素的layout⽅法即可。
onTouchEvent⽅法,则是书写移动以及操作结束时的View动态变化的过程。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论