文章收藏的好句子:方向对了,慢其实就是快。
ps:本篇文章的 demo 是用 AndroidStudio 工具开发的。
这里在Android中的自定义View(一)这篇文章的基础上再继续写一下自定义 View 的案例,这里的自定义 ViewGroup 直接继承于 ViewGroup,我们写一个类似 ViewPager 这样的自定义 ViewGroup,可以让它左右滑动。
(1)新建一个 Activity ,名叫 DemoActivity;
public class DemoActivity extends Activity {
private MyViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
initView();
}
private void initView() {
LayoutInflater inflater = getLayoutInflater();
mViewPager = (MyViewPager) findViewById(R.id.viewPager);
//1、
final int screenWidth = getScreenMetrics(this).widthPixels;
for (int i = 0; i < 4; i++) {
View layout =inflater.inflate(
R.layout.content_layout, mViewPager, false);
layout.getLayoutParams().width = screenWidth;
TextView textView = (TextView) layout.findViewById(R.id.tv);
textView.setText("第" +(i+1) + "页面");
//2、
mViewPager.addView(layout);
}
}
public DisplayMetrics getScreenMetrics(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm;
}
}
注释1表示获取手机屏幕的宽度;注释2表示将创建的 View 添加到 MyViewPager 中。
(2)新建一个布局文件 activity_demo.xml;
<LinearLayout 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"
android:background="#ffffff"
android:orientation="vertical" >
<com.xiaoer.MyViewPager
android:id="@+id/viewPager"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
(3)新建一个类 MyViewPager并继承于 ViewGroup;
public class MyViewPager extends ViewGroup {
private int mChildrenSize;
private int mChildWidth;
private int mChildIndex;
private int mLastX = 0;
private int mLastY = 0;
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
public MyViewPager(Context context) {
super(context);
init();
}
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyViewPager(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
if (mScroller == null) {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
}
//3、
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
//4、
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//5、
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
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();
//6、
mVelocityTracker.computeCurrentVelocity(1000);
//7、
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
//8、
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int maxChildWidth = 0;
int maxChildHeight = 0;
//14、
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getMeasuredHeight() > maxChildHeight) {
maxChildHeight = child.getMeasuredHeight();
}
if (child.getMeasuredWidth() > maxChildWidth) {
maxChildWidth = child.getMeasuredWidth();
}
}
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
} else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
measuredWidth = maxChildWidth * childCount;
measuredHeight = maxChildHeight;
//15、
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredHeight = maxChildHeight;
//16、
setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
measuredWidth = maxChildWidth * childCount;
//17、
setMeasuredDimension(measuredWidth, heightSpaceSize);
}
}
@Override
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 < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildWidth = childWidth;
//18、
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private void smoothScrollBy(int dx, int dy) {
//9、
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
//10、
invalidate();
}
//11、
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
//12、
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//13、
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}
注释3的 onInterceptTouchEvent 方法是是否拦截子元素触摸的方法,如果返回值是 true,那么就拦截子元素触摸;如果返回值是 false,那么就不拦截子元素触摸。
注释4和注释5的代码都表示拦截子元素触摸;注释6表示1s的时间内运行来多少个像素点;注释7表示获取X轴上的速率;注释8表示获取X轴上移动的距离;注释9中的 mScroller 是一个辅助动画的类,startScroll 方法并没有开始移动,只是将该方法的参数保存到 mScroller 这个对象中。
注释10的方法执行后最终调用注释11的方法;注释12的方法最终完成一步步的移动;注释13的代码执行后最终调用注释11的代码,直到注释6代码中的 mVelocityTracker.computeCurrentVelocity(1000) 这个参数1000毫秒过后,就不在调用注释13的代码了,也就是 mScroller.computeScrollOffset() 为 false。
注释14的 for 循环是为了获取子 View 的最大宽度和最大高度;注释15,当 MyViewPager 宽和高的模式为 MeasureSpec.AT_MOST 时,将子 View 中最大的宽度乘积子 View 的个数作为 MyViewPager 的宽度,将子 View 中最大的高度作为 MyViewPager 的高度。
注释16,当 MyViewPager 高的模式为 MeasureSpec.AT_MOST 时,将子 View 中最大的高度作为 MyViewPager 的高度;注释17,当 MyViewPager 宽的模式为 MeasureSpec.AT_MOST 时,将子 View 中最大的宽度乘积子 View 的个数作为 MyViewPager 的宽度;注释18,执行子 View 的 layout 过程。
程序运行结果如下所示;
第一次用手指向左滑动就可以看到效果了。
上面的代码并规范,第一点是没有子元素的时候不应该直接把宽和高设为0,
而应该根据 LayoutParams 中的宽和高来做相应处理,第二点是在测量 MyViewPager 的宽和高时没有考虑到它的 padding 以及子元素的 margin,因为它的 padding 以及子元素的 margin会影响到 MyViewPager 的宽和高。