您的位置:首页 > 运维架构 > 网站架构

Android控件架构与自定义控件详解(三)自定义ViewGroup

2017-02-02 18:18 381 查看
之前分析了如何自定义View,下面来分析如何创建自定义ViewGroup,ViewGroup存在的目的就是为了对其子View进行管理,为其子View添加显示、响应的规则。因此,自定义ViewGroup通常需要重写onMeasure()方法来对子View进行测量重写onLayout()方法来确定子View的位置重写onTouchEvent()方法增加响应事件

下面将实现一个类似Android原生控件ScrollView的自定义ViewGroup,并且在滑动的过程中,增加一个黏性效果,即滑动大于一定的距离才会触发正常滑动,否则自动弹回开始的位置,效果图如下:



代码如下:

package com.example.customviewgroup;

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Scroller;

/**
* Created by gavinandre on 17-1-29.
*/

public class MyScrollView extends ViewGroup {

private int mScreenHeight;
private Scroller mScroller;
private int mLastY;
private int mStart;
private int mEnd;

public MyScrollView(Context context) {
this(context, null);
}

public MyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}

private void initView(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenHeight = dm.heightPixels;
mScroller = new Scroller(context);
}

/**
* 使用遍历方式通知子View对自身进行测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; ++i) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}

/**
* 测量完毕后对子View进行放置位置的设定
* 本例让每个View都显示完整的一屏
* 使用遍历的方式通知子View对自身进行布局设置
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
//设置ViewGroup的高度
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.height = mScreenHeight * childCount;
setLayoutParams(mlp);
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
//修改每个子View的top和bottom属性,让它们能依次排列下来
child.layout(l, i * mScreenHeight, r, (i + 1) * mScreenHeight);
}
}
}

/**
* 在ViewGroup中添加滑动事件
* down事件记录触摸位置
* move事件使用scrollBy(0,dy)方法,实现手指滑动时所有子View跟着滑动
* up事件实现黏性效果
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = y;
mStart = getScrollY();
break;
case MotionEvent.ACTION_MOVE:
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
int dy = mLastY - y;
if (getScrollY() < 0) {
dy = 0;
}
if (getScrollY() > getHeight() - mScreenHeight) {
dy = 0;
}
scrollBy(0, dy);
mLastY = y;
break;
case MotionEvent.ACTION_UP:
int dScrollY = checkAlignment();
//滑动距离大于子View的1 / 3,则使用Scroller类来平滑到下一个子View,否则就会回滚到原来的位置
if (dScrollY > 0) {
//向上滑动
if (dScrollY < mScreenHeight / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, mScreenHeight - dScrollY);
}
} else {
//向下滑动
if (-dScrollY < mScreenHeight / 3) {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, -mScreenHeight - dScrollY);
}
}
break;
}
postInvalidate();
return true;
}

/**
* 获取触摸时滑动的距离
*/
private int checkAlignment() {
int mEnd = getScrollY();
boolean isUp = ((mEnd - mStart) > 0);
int lastPrev = mEnd % mScreenHeight;
int lastNext = mScreenHeight - lastPrev;
if (isUp) {
//向上的
return lastPrev;
} else {
return -lastNext;
}
}

@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
postInvalidate();
}
}
}


代码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: