自定义控件仿照广告条ViewPager--手势识别器OnGestureListener

分析图:

自定义控件类:MyScrollView.java

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/*
模拟ViewPager的效果:
实现步骤:
1、自定义view继承viewGroup。
2、重写onLayout方法,为每一个子View确定位置。
3、重写onTouchEvent方法,监听touch事件,并用scrollTo()或scrollBy()方法移动view,
4、监听UP事件,当手指抬起时,判断应显示的页面位置,并计算距离、滑动页面。
5、添加页面切换的监听事件。

知识点:1.手势识别器  2.scrollBy与scrollTo方法
scrollBy内部调用了scrollTo方法,mScrollX与mScrollY代表当前view的基准点
public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);//在当前位置移动(x,y)的距离
}
3.系统带有的滑动效果为:new Scroller(context)  方法名和下面定义的一样,不过内部更复杂,还考虑重力等等
 */
public class MyScrollView extends ViewGroup{
    private Context context;
    private GestureDetector detector;//手势识别器
    
    //在xml布局文件中定义该控件时会调用此构造方法
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        myScroller=new MyScrollUtils(context);
        initView();
    }
    /**
     * 是否中断onTouch事件的传递,解决子控件把父控件的事件吃掉
     * 返回true 的时候中断事件,执行自己的onTouchEvent方法
     * 返回false 的时候,默认处理,不中断也不会执行自己的onTouchEvent方法,会传给子控件
     */
    int tempX=0,tempY=0;
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        super.onInterceptTouchEvent(ev);
        boolean result=false;//默认不中断
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            tempX=(int) ev.getX();
            tempY=(int) ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            //手指在屏幕上水平方向的绝对值
            int disX=(int) Math.abs(tempX-ev.getX());
            //手指在屏幕上垂直方向的绝对值
            int disY=(int) Math.abs(tempY-ev.getY());
            if(disX>disY && disX>10){
         firstX=temp=tempX;//不加,滑动listView会有跳动,因为执行不到下面触摸事件的按下,所以要赋值 result
=true;//中断事件的传递,执行自己的触摸事件 }else{ result=false; } break; case MotionEvent.ACTION_UP: break; } return result; } /** * 计算子view的大小,不然放在该viewGroup上面的控件显示不出来 控制在这个布局里的子控件的长宽大小, 
      如果在该方法中不调用super.OnMeasure 只写上setMeasuredDimension(200, 200);这样的话就把View默认的测量流程覆盖掉了,
      不管在xml布局文件中定义MyScrollView这个视图的大小是多少,最终在界面上显示的大小都将会是200*200。
*/ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); for (int i = 0; i <getChildCount(); i++) { View v=getChildAt(i); v.measure(widthMeasureSpec, heightMeasureSpec); } } /**view.layout(...) 父view会根据子view的需求和自身的情况,来综合确定子view的位置 * 对子View进行布局,确定子View的位置.因为该控件继承的ViewGroup * changed 若为true,说明布局发生了变化 * l,t,r,b 左上右下 是指当前viewGroup在其父view中的位置(一般不用) */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0; i <this.getChildCount(); i++) { //得到该布局的子控件(也就是MainActivity类中往该控件中添加的图片) View view=getChildAt(i); //指定子view在父布局中的位置,左上右下四条边的位置,是指在viewGroup坐标系中的位置 view.layout(i*getWidth(),0, getWidth()+i*getWidth(),getHeight());//确定子控件的位置 //代表宽度从i*getWidth()到getWidth()+i*getWidth(), 高度从0到getHeight() } } //下面方法有返回类型的,如果返回为true就代表吃掉了该事件,而没带参数的可以接收该事件 private void initView() { detector=new GestureDetector(context, new OnGestureListener() { @Override//有手指(当多个手指触摸到屏幕的时候)抬起的时候调用 public boolean onSingleTapUp(MotionEvent e) { //System.out.println("onSingleTapUp-有手指抬起"); return true; } @Override//手指触碰到屏幕上调用 public void onShowPress(MotionEvent e) { //System.out.println("onShowPress--没松开或者拖动"); } @Override//滑动的时候调用 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { System.out.println("滑动"); /** * 移动当前view的内容 * distanceX X方向滑动的距离 * distanceY Y方向滑动的距离 */ scrollBy((int) distanceX, 0);//让当前的屏幕移动distanceX的距离 return true; } @Override//长按的时候调用 public void onLongPress(MotionEvent e) { //System.out.println("onLongPress--长按"); } @Override//快速滑动调用,e1,e2代表起始点与结束点, velocityX代表x上的速度 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //System.out.println("onFling--快速滑动"); return true; } @Override//按下的时候调用 public boolean onDown(MotionEvent e) { System.out.println("onDown--按下"); /*Message mes=handler.obtainMessage(); handler.sendMessage(mes);*/ return true; } }, null);//如果想在子线程完成就可以new一个Handler实现通讯 } /** * 标记当前屏幕显示的是第几张图片(也就是标记子控件) */ private int currIndex=0; private int firstX = 0,temp=0;//记录按下的位置 @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); //detector.onTouchEvent(event);//手势识别器,系统封装好的工具类,交给它解析触摸动作 //添加自己的事件(主要是松手)解析 switch (event.getAction()) { case MotionEvent.ACTION_DOWN://按下 temp=firstX=(int) event.getX(); break; case MotionEvent.ACTION_MOVE://移动 scrollBy(temp-(int) event.getX(), 0);//在当前视图位置的基础上移动滑动的距离 temp=(int) event.getX();//记录滑动的位置 break; case MotionEvent.ACTION_UP://松手 if(firstX-event.getX()>getWidth()/2){//按下的大于松开的,且滑动的距离大于半个屏幕 currIndex++;//向右滑 }else if(event.getX()-firstX>getWidth()/2){//按下的小于松开的 currIndex--;//向左滑 } System.out.println("currIndex="+currIndex); moveTo(currIndex);//移动到这个屏幕 break; } return true; } private MyScrollUtils myScroller; /** *需要判断当前屏幕的下标有没有越界,0<=index<getChildCount() *移动到指定的屏幕上 */ private void moveTo(int index) { if(index<0){ currIndex=0; }else if(index>=getChildCount()){ currIndex=getChildCount()-1; } //瞬间移动 //scrollTo(currIndex*getWidth(), 0);//移动到这个坐标点 //动画移动 int distance=(int) (currIndex*getWidth()-getScrollX());//移动的目的地-当前位置=需要移动的距离 myScroller.startScroll(getScrollX(),0,distance,0);//开始执行动画 invalidate();//刷新视图,会执行onDrow方法(重新绘制),也会执行computeScroll方法 } /** *invalidate();会导致该方法的执行(计算滑动) */ @Override public void computeScroll() { if(myScroller.computeScrollOffset()){//还没有完成移动 scrollTo(myScroller.getNewX(), myScroller.getNewY());//移动到计算出来的这个位置 invalidate();//使无效,刷新视图,将自动调用computeScroll方法,实现循环,直到完成移动 } } }

2.移动效果控制类 MyScrollUtils.java

import android.content.Context;
import android.os.SystemClock;

/**
移动效果操作的工具类
 */
public class MyScrollUtils {
    private  int startX;
    private  int startY;
    private Context context;
    private int distanceY;
    private int distanceX;
    private long startTime;
    /**
     * 判断是否完成动画的执行
     * true:完成
     * false:没有完成
     */
    private boolean isFinish;
    private long moveTime=500;//定义整个视图移动的时间为500ms
    
    public MyScrollUtils(Context context) {
        this.context=context;
    }

    /**
     * 开始移动(慢慢的移动)
     * @param startX          开始时的X坐标(移动之前的位置)
     * @param startY        开始时的Y坐标
     * @param distanceX        X方向要移动的距离
     * @param distanceY        Y方向要移动的距离
     */
    public void startScroll(float startX, int startY, int distanceX, int distanceY) {
        this.startX=(int) startX;
        this.startY=startY;
        this.distanceX=distanceX;
        this.distanceY=distanceY;
        this.startTime=SystemClock.uptimeMillis();//开机至今的时间
        this.isFinish=false;//没有完成动画的执行
    }
    
    private int newX;
    private int newY;
    
    
    public int getNewX() {
        return newX;
    }

    public void setNewX(int newX) {
        this.newX = newX;
    }

    public int getNewY() {
        return newY;
    }

    public void setNewY(int newY) {
        this.newY = newY;
    }

    /**
     * 计算偏移量,当前的运行状况
     * 返回值:true 代表还在运行     false代表运行结束
     * 默认定义为500ms完成整个移动
     */
    public boolean computeScrollOffset() {
        if(isFinish){
            return false;
        }
        //计算位移
        long time=SystemClock.uptimeMillis()-startTime;//已经移动的时间
        if(time>=moveTime){//时间超过定义的移动时间
            newX=startX+distanceX;
            newY=startY+distanceY;
            isFinish=true;//标记完成移动
        }else{//没有超过就每次移动一点距离
            newX=(int) (startX+time*distanceX/moveTime);//开始位置+时间*速度(移动的距离)=这一次移动的位置
            newY=(int) (startY+time*distanceY/moveTime);//Y方向上移动所到的位置
        }
        return true;
    }    
}

3.活动类,使用自己的控件 MainActivity.java

import android.os.Bundle;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.widget.ImageView;
/*
出现的问题:listView可以上下滑动,可是在listView上不能左右滑动myView了
解决方法:
判断触摸的手势,如果是横向滑动,就把该touch事件中断掉,让子控件收不到该事件,自己就可以处理了
即:重写该控件的onInterceptTouchEvent方法
 */
public class MainActivity extends Activity {

    private MyScrollView myView;//自定义的控件
    private int[] imgArr={R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();//初始化数据
    }

    private void init() {
        myView = (MyScrollView) findViewById(R.id.myview);
        for (int i = 0; i < imgArr.length; i++) {
            ImageView image=new ImageView(this);
            image.setBackgroundResource(imgArr[i]);
            myView.addView(image);//为自定义控件添加子控件
        }
        //改自定义的MyScrollView添加测试的子布局
        View v = getLayoutInflater().inflate(R.layout.temp_layout, null);
        myView.addView(v, 2);//在第三张图片上添加子布局(因为是作为第2个子view)
        
    }
    
    
}

布局文件:activity_main.xml

<RelativeLayout 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"
    tools:context=".MainActivity" >

    <com.example.mykj.MyScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/myview"/>

</RelativeLayout>

在自定义控件中添加测试布局 temp_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="测试的按钮" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试的文本"
        android:textSize="30sp" />

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/list_test" />

</LinearLayout>

效果图:

             

posted @ 2016-07-31 21:43  ts-android  阅读(226)  评论(0编辑  收藏  举报