android常用控件可能在开发过程中不能满足我们的需求,所以常常需要自定义控件,这里总结了使用viewGroup自定义控件的方法,以及我的个人经验的总结。
首先介绍viewGroup
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.LinearLayout
可以看到viewGroup是我们常见五种布局的基类,所以ViewGroup有的方法,他的儿子也会被继承,下面继续介绍我们自定义时需要复写的6个方法。
在自定义ViewGrop过程中,我们通常需要把几个控件组合到一起,从ViewGrop的名字就可以看出,这个view就是用来装的,装几个view到一个ViewGrop中,装完了的ViewGrop就是你的自定义view了,这个是基本思路。在装的过程中涉及到了其他一些知识,例如:事件的分发机制、ViewGroupHelper的使用;其中事件的分发机制略显复杂,不过如果想在自定义控件中加入滑动效果,事件分发机制必须弄清楚了。
常用六大复写方法(排名有先后)
void onFinishInflate()
Finalize inflating a view from XML. This is called as the last phase of inflation, after all child views have been added.
Even if the subclass overrides onFinishInflate, they should always be sure to call the super method, so that we get called.
总结:完成所有view的inflate后调用,这里的inflate只是将view从xml中影射出来,view的具体大小和摆放位置在后面两个函数决定。不过这里可以确定View对象了,使用getChildAt(x)获取。
void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
Measure the view and its content to determine the measured width and the measured height. This method is invoked by measure(int, int) and should be overriden by subclasses to provide accurate and efficient measurement of their contents.
总结:根据xml信息确定view的尺寸。很直白就是可以得到view的大小了
onLayout(boolean changed, int l, int t, int r, int b)
Called from layout when this view should assign a size and position to each of its children. Derived classes with children should override this method and call layout on each of their children.
总结:view对象在1中被实例化了,在2中确定大小了,这里就是确定view在该layout中如何摆放。这个函数就是为什么线性布局是顺序排放,相对布局是相对排放,就是在这里产生差别的。
boolean DragLayout.onInterceptTouchEvent(MotionEvent ev)
Implement this method to intercept all touch screen motion events. This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.
总结:如果你看过android事件分发机制了,那么这个函数就不需要我总结了,而且这个也不是一句话就能说清楚的,不过还是要说的。当event传递到我们的ViewGroup布局中,调用该方法确定是否要拦截,返回true时,事件将不再继续向下传递,返回false,事件继续向下传递。
boolean onTouchEvent(MotionEvent event)
Implement this method to handle touch screen motion events.
总结:在4中如果知道事件是否需要拦截呢,可以根据这里的对event的判断,来决定。这个函数还可以对具体的触摸事件作出不同响应
void computeScroll()
Called by a parent to request that a child update its values for mScrollX and mScrollY if necessary. This will typically be done if the child is animating a scroll using a Scroller object.
总结:这里我还没弄明白,反正如果没这个函数,还想让view在layout中移动是失败的,我使用的是ViewDragHelper.smoothSlideViewTo()是失败的。
下面是个自定义例子,实现了一个layout的左滑删除
package com.example.viewdragtest;
import javax.security.auth.callback.Callback;
import android.content.ContentValues;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.transition.Slide;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
public class VDHLayout extends LinearLayout {
private ViewDragHelper mDragger;
private View contentView;
private View actionView;
private int draggedX;
private int dragDistance;//控件大小
private final int AUTO_OPEN_SPEED_LIMIT = 400;//速度
private onSlideListener onSlide;
private float downX;
private float downY;
public VDHLayout(Context context, AttributeSet attrs) {
super(context, attrs, -1);
// TODO Auto-generated constructor stub
mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int arg1) {
// TODO Auto-generated method stub
Log.v("drag", "This is tryCaptureView!");
return child == contentView || child == actionView;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
actionView.setVisibility(View.VISIBLE);
draggedX = left;
// 拦截父视图事件,不让父试图事件影响
getParent().requestDisallowInterceptTouchEvent(true);
if(changedView == contentView){
Log.v("drag", "This is onViewPositionChanged! view = contentView; left = " + left);
actionView.offsetLeftAndRight(dx);
}else if(changedView == actionView){
Log.v("drag", "This is onViewPositionChanged! view = actionView; left = " + left);
contentView.offsetLeftAndRight(dx);
}
if(actionView.getVisibility() == View.GONE){
actionView.setVisibility(View.VISIBLE);
}
invalidate();
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
Log.v("drag", "This is clampViewPositionHorizontal!");
// TODO Auto-generated method stub
if(child == contentView){
int leftBound = -getPaddingLeft() - dragDistance;
return Math.min(Math.max(leftBound, left), 0);
}else {
int minLeftBound = actionView.getPaddingLeft() + contentView.getWidth() - dragDistance;
int maxLeftBound = actionView.getPaddingLeft() + contentView.getWidth() + actionView.getPaddingRight();
return Math.min(Math.max(minLeftBound, left), maxLeftBound);
}
}
// @Override
// public int getViewHorizontalDragRange(View child) {
// // TODO Auto-generated method stub
// return dragDistance;
// }
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// TODO Auto-generated method stub
super.onViewReleased(releasedChild, xvel, yvel);
boolean settleToOpen = false;
if(xvel < -AUTO_OPEN_SPEED_LIMIT || draggedX < -dragDistance/2){
settleToOpen = true;
}
if(onSlide != null){
onSlide.onSlided(settleToOpen);
}
final int destX = settleToOpen ? - dragDistance : 0;
Log.v("drag", "This is onViewReleased! destX = " + destX);
mDragger.smoothSlideViewTo(contentView, destX, 0);
ViewCompat.postInvalidateOnAnimation(VDHLayout.this);
}
}); //end listener
}//end construct
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
contentView = getChildAt(0);
actionView = getChildAt(1);
actionView.setVisibility(View.GONE);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
dragDistance = actionView.getMeasuredWidth();
Log.v("drag", "dragDistance = " + dragDistance);
}
// @Override
// protected void onLayout(boolean changed, int l, int t, int r, int b) {
// // TODO Auto-generated method stub
// super.onLayout(changed, l, t, r, b);
// }
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
getParent().requestDisallowInterceptTouchEvent(false);
if(mDragger.shouldInterceptTouchEvent(ev)){
return true;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_DOWN){
downX = event.getRawX();
downY = event.getRawY();
}
if(event.getAction() == MotionEvent.ACTION_UP){
if(Math.abs(downX - event.getRawX()) < 10 &&
Math.abs(downY - event.getRawY()) < 10){
onSlide.onClick();
}
}
mDragger.processTouchEvent(event);
return true;
}
public void revert(){
if(mDragger != null){
mDragger.smoothSlideViewTo(contentView, 0, 0);
invalidate();
}
}
public void setOnSlide(onSlideListener onSlide) {
this.onSlide = onSlide;
}
/**
* 由于整个视图都用了ViewDragHelper手势处理,
* 所以导致不滑动的视图点击事件不可用,所以需要自己处理点击事件
*/
public interface onSlideListener {
/**
* 侧滑完了之后调用 true已经侧滑,false还未侧滑
*/
void onSlided(boolean isSlide);
/**
* 未侧滑状态下的默认显示整体的点击事件
*/
void onClick();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mDragger.continueSettling(true)) {
/**
* 导致失效发生在接下来的动画时间步,通常下显示帧。 这个方法可以从外部的调用UI线程只有当这种观点是附加到一个窗口。
*/
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
代码中使用了ViewDragHelper类
下面顺便说下ViewDragHelper类,这个类是辅助我们的view来实现滑动效果的,在view和ViewDragHelper中间是通过ViewDragHelper.callback来链接的。个人理解就是ViewDragHelper来帮助View来进行touch事件的处理,在早期的android开发中touch事件处理是很麻烦的,这对移动设备开发是很拖慢节奏的,所以谷歌儿推出这个类来帮助草根程序员。
ViewDragHelper的常用回调函数
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
Called when the captured view’s position changes as the result of a drag or settle.
总结:判断changedView是否需要拦截事件
void onViewPositionChanged (View changedView, int left, int top, int dx, int dy)
Called when the captured view’s position changes as the result of a drag or settle.
总结:更改view的位置
int clampViewPositionHorizontal(View child, int left, int dx)
Restrict the motion of the dragged child view along the horizontal axis.
总结:判断view的水平移动范围,还有垂直范围,这里就是不写了
void onViewReleased(View releasedChild, float xvel, float yvel)
Called when the child view is no longer being actively dragged.
总结:当手指从view上移开时,调用此函数。例如下拉刷新。
例子程序,和上面那个是同一个。。。。。。。。。