继承已有ViewGroup实现自定义控件
模拟ViewPager的效果:
实现步骤:
1、自定义view继承viewGroup。
2、重写onLayout方法,为每一个子View确定位置。
3、重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view,
4、监听UP事件,当手指抬起时,判断应显示的页面位置,并计算距离、滑动页面。
5、添加页面切换的监听事件。
布局
<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:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity" >
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
</RadioGroup>
<com.itheima.myscrollview28.MyScrollView
android:id="@+id/myscroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
还有一个页面随便添加一些控件,listview,放在中间,来练习onTouchEvent事件传递
MyScroller:计算位移距离的工具类,这个是匀速运动
new Scroller(ctx);//这个是系统的,自定义起的方法名都和它起的一样,能快速切换
public class MyScroller {
private int startX;
private int startY;
private int distanceX;
private int distanceY;
/**
* 开始执行动画的时间
*/
private long startTime;
/**
* 判断是否正在执行动画
* true 是还在运行
* false 已经停止
*/
private boolean isFinish;
public MyScroller(Context ctx){
}
/**
* 开移移动:将这些信息记录下来
* @param startX 开始时的X坐标
* @param startY 开始时的Y坐标
* @param disX X方向 要移动的距离
* @param disY Y方向 要移动的距离
*/
public void startScroll(int startX, int startY, int disX, int disY) {
this.startX = startX;
this.startY = startY;
this.distanceX = disX;
this.distanceY = disY;
this.startTime = SystemClock.uptimeMillis();
this.isFinish = false;
}
/**
* 默认运行的时间
* 毫秒值
*/
private int duration = 500;
/**
* 当前的X值
*/
private long currX;
/**
* 当前的Y值
*/
private long currY;
/**
* 计算一下当前的运行状况
* 返回值:
* true 还在运行
* false 运行结束
*/
public boolean computeScrollOffset() {
if (isFinish) {
return false;
}
// 获得所用的时间
long passTime = SystemClock.uptimeMillis() - startTime;
// 如果时间还在允许的范围内
if (passTime < duration) {
// 当前的位置 = 开始的位置 + 移动的距离(距离 = 速度*时间)
currX = startX + distanceX * passTime / duration;
currY = startY + distanceY * passTime / duration;
} else {
currX = startX + distanceX;
currY = startY + distanceY;
isFinish = true;
}
return true;
}
public long getCurrX() {
return currX;
}
public void setCurrX(long currX) {
this.currX = currX;
}
}
MyScrollView:手势识别器其实就是onTouchEvent提供的简单工具类
public class MyScrollView extends ViewGroup{
private Context ctx;
/**
* 判断是否发生快速滑动
*/
protected boolean isFling;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
this.ctx = context;
initView();
}
private void initView() {
// myScroller = new MyScroller(ctx);
myScroller = new Scroller(ctx);//这个是系统的,自定义的方法名都和它起的的一样
detector = new GestureDetector(ctx, new OnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
/**
* 响应手指在屏幕上的滑动事件
*/
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
//移动屏幕
// System.out.println("distanceX::"+distanceX);
/**
* 移动当前view内容 移动一段距离
* disX X方向移的距离 为正是,图片向左移动,为负时,图片向右移动
* disY Y方向移动的距离
*/
scrollBy((int) distanceX, 0);
/**
* 将当前视图的基准点移动到某个点 坐标点
* x 水平方向X坐标
* Y 竖直方向Y坐标
* scrollTo(x, y);
*/
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
/**
* 发生快速滑动时的回调
*/
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
isFling = true;
if(velocityX>0 && currId>0){ // 快速向右滑动
currId--;
}else if(velocityX<0 && currId<getChildCount()-1){ // 快速向左滑动
currId++;
}
moveToDest(currId);
return false;
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
});
}
@Override
/**
* 计算 控件大小,
* 做为viewGroup 还有一个责任,,:计算 子view的大小
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
v.measure(widthMeasureSpec, heightMeasureSpec);
// v.getMeasuredWidth() // 得到测量的大小
}
}
@Override
/**
* 对子view进行布局,确定子view的位置
* changed 若为true ,说明布局发生了变化
* l\t\r\b\ 是指当前viewgroup 在其父view中的位置
*/
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i); // 取得下标为I的子view
/**
* 父view 会根据子view的需求,和自身的情况,来综合确定子view的位置,(确定他的大小)
*/
//指定子view的位置 , 左,上,右,下,是指在viewGround坐标系中的位置
view.layout(0+i*getWidth(), 0, getWidth()+i*getWidth(), getHeight());
// view.getWidth(); 得到view的真实的大小。
}
}
/**
* 手势识别的工具类
*/
private GestureDetector detector;
/**
* 当前的ID值
* 显示在屏幕上的子View的下标
*/
private int currId = 0;
/**
* down 事件时的x坐标
*/
private int firstX = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
detector.onTouchEvent(event);
//添加自己的事件解析
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if(!isFling){// 在没有发生快速滑动的时候,才执行按位置判断currid
int nextId = 0;
if(event.getX()-firstX>getWidth()/2){ // 手指向右滑动,超过屏幕的1/2 当前的currid - 1
nextId = currId-1;
}else if(firstX - event.getX()>getWidth()/2){ // 手指向左滑动,超过屏幕的1/2 当前的currid + 1
nextId = currId+1;
}else{
nextId = currId;
}
moveToDest(nextId);
// scrollTo(0, 0);
}
isFling = false;
break;
}
return true;
}
/**
* 计算位移的工具类
*/
// private MyScroller myScroller;
private Scroller myScroller;
/**
* 移动到指定的屏幕上
* @param nextId 屏幕 的下标
*/
public void moveToDest(int nextId) {
/*
* 对 nextId 进行判断 ,确保 是在合理的范围
* 即 nextId >=0 && next <=getChildCount()-1
*/
//确保 currId>=0
currId = (nextId>=0)?nextId:0;
//确保 currId<=getChildCount()-1
currId = (nextId<=getChildCount()-1)?nextId:(getChildCount()-1);
//瞬间移动
// scrollTo(currId*getWidth(), 0);
//触发listener事件
if(pageChangedListener!=null){
pageChangedListener.moveToDest(currId);
}
int distance = currId*getWidth() - getScrollX(); // 最终的位置 - 现在的位置 = 要移动的距离
// myScroller.startScroll(getScrollX(),0,distance,0);
//设置运行的时间
myScroller.startScroll(getScrollX(),0,distance,0,Math.abs(distance));
/*
* 刷新当前view onDraw()方法 的执行
*/
invalidate();
}
@Override
/**
* invalidate(); 会导致 computeScroll()这个方法的执行
*/
public void computeScroll() {
if(myScroller.computeScrollOffset()){
int newX = (int) myScroller.getCurrX();
System.out.println("newX::"+newX);
scrollTo(newX, 0);
invalidate();
};
}
public MyPageChangedListener getPageChangedListener() {
return pageChangedListener;
}
public void setPageChangedListener(MyPageChangedListener pageChangedListener) {
this.pageChangedListener = pageChangedListener;
}
private MyPageChangedListener pageChangedListener;
/**
* 页面改时时的监听接口
* @author leo
*
*/
public interface MyPageChangedListener{
void moveToDest(int currid);
}
}
使用手势识别器GestureDetector
/**
* 设置引导页的基类, 不需要在清单文件中注册, 因为不需要界面展示
* 让其他Activity继承它就行
* @author Kevin
*
*/
public abstract class BaseSetupActivity extends Activity {
private GestureDetector mDectector;
public SharedPreferences mPref;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPref = getSharedPreferences("config", MODE_PRIVATE);
// 手势识别器
mDectector = new GestureDetector(this, new SimpleOnGestureListener() {
/**
* 监听手势滑动事件 e1表示滑动的起点,e2表示滑动终点 velocityX表示水平速度 velocityY表示垂直速度
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
// 判断纵向滑动幅度是否过大, 过大的话不允许切换界面
if (Math.abs(e2.getRawY() - e1.getRawY()) > 100) {
Toast.makeText(BaseSetupActivity.this, "不能这样划哦!",
Toast.LENGTH_SHORT).show();
return true;
}
// 判断滑动是否过慢
if (Math.abs(velocityX) < 100) {
Toast.makeText(BaseSetupActivity.this, "滑动的太慢了!",
Toast.LENGTH_SHORT).show();
return true;
}
// 向右划,上一页
if (e2.getRawX() - e1.getRawX() > 200) {
showPreviousPage();
return true;
}
// 向左划, 下一页
if (e1.getRawX() - e2.getRawX() > 200) {
showNextPage();
return true;
}
return super.onFling(e1, e2, velocityX, velocityY);
}
});
}
/**
* 展示下一页, 子类必须实现,比如动画的逻辑可以写在这里
*/
public abstract void showNextPage();
/**
* 展示上一页, 子类必须实现
*/
public abstract void showPreviousPage();
//几乎每个页面都有按钮,所以把按钮监听放到这里,代码会简化很多
// 点击下一页按钮
public void next(View view) {
showNextPage();
}
// 点击上一页按钮
public void previous(View view) {
showPreviousPage();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDectector.onTouchEvent(event);// 委托手势识别器处理触摸事件
return super.onTouchEvent(event);
}
}
一定要及得委托手势识别器,否则上边的代码没用
onTouchEvent
更多看智慧北京-滑动事件处理
时间: 2024-10-14 12:02:42