自定义控件---------ViewPager的一个小案例

来源:互联网 发布:java log4j用法 编辑:程序博客网 时间:2024/05/22 11:36

今天做了一个关于VIewPager的小demo        感觉有些难以理解所有在这里分享出来,大家需要的就带走吧大笑


首先说下具体的功能吧

1、几张图片滑动切换   调用系统的api按系统默认的250毫秒滑动切换到下一张,自己写一个匀速切换到下一张(两种)

2、添加导航按钮,滑动到下一张的时候,导航的按钮自动切换到相应的位置上

3、在几张图片当中添加页面(这里添加一个自定义的测试页面)里面用ScrollView来模拟ListView实现效果,下面用listview来解释


大体的功能就是上面的几个吧,但是详细的细节却很重要哦,这个小demo搞了我一天了。。。。。有些地方还不是很理解,这里只代表是我个人的理解,也欢迎各位多提意见,thank!


下面先来看一张最终的效果图:


好了,现在先来说下具体怎么实现的:

这是一个自定的ViewPager:

1、写一个类继承ViewGroup,并实现其两个参数的构造方法

public class MyScorllView extends ViewGroup {public MyScorllView(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {}  }

2、引入到布局文件中

<com.ittest.MyScorllView        android:id="@+id/msv"        android:layout_width="fill_parent"        android:layout_height="wrap_content" />

3、到对应的Activity类中实例化id,并引入图片资源数组,添加到MyScorllView

public class MainActivity extends Activity {private MyScorllView msv;// 图片资源ID 数组private int[] ids = new int[] { R.drawable.a1, R.drawable.a2,R.drawable.a3, R.drawable.a4, R.drawable.a5, R.drawable.a6 };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);                msv = (MyScorllView) findViewById(R.id.msv);                //添加6张图片        for (int i = 0; i < ids.length; i++) {ImageView image = new ImageView(this);//设置背景资源才能完美的填充到布局中,而src则不能image.setBackgroundResource(ids[i]);//添加给MyScorllViewmsv.addView(image);}        }  }


4、指定子view的位置(onLayout()方法)

通过第三步,到这里MyScorllView中已获得了图片的资源

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {//用for循环动态获取位置for (int i = 0; i < getChildCount(); i++) {View view = getChildAt(i);//给当前view设置布局(左、上、右、下)view.layout(0+(i*getWidth()), 0, getWidth()+(i*getWidth()), getHeight());}}

5、给图片添加滑动事件

detector = new GestureDetector(ctx, new GestureDetector.OnGestureListener() {@Overridepublic boolean onSingleTapUp(MotionEvent e) {// TODO Auto-generated method stubreturn false;}@Overridepublic void onShowPress(MotionEvent e) {// TODO Auto-generated method stub}/** * 正在滑动时回调该方法 */@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY) {/** * scrollBy(int i ,int j)方法让当前view的内容发生移动 * distanceX x方向移动的距离 * distanceY y方向移动的距离 */scrollBy((int) distanceX, 0);//只关心x方向上的移动/** * scrollTo(x,y) 让当前的view的内容移动到某点上 * x 目标点的x坐标 * y 目标点的y坐标 */return false;}@Overridepublic void onLongPress(MotionEvent e) {// TODO Auto-generated method stub}/** * 发生快速滑动时回调此方法 */@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {isFling = true;System.out.println("velocityX:"+velocityX);if(velocityX > 0 && currIndex > 0){currIndex--;}else if(velocityX < 0 && currIndex < getChildCount()-1){currIndex++;}//让view移动到相应的位置上去moveToDest(currIndex);return false;}@Overridepublic boolean onDown(MotionEvent e) {// TODO Auto-generated method stubreturn false;}});#@Overridepublic boolean onTouchEvent(MotionEvent event) {//用detector来解析正常的滑动点击事件detector.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:firstX = lastX = (int) event.getX();isFling = false;break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP://没有快速滑动的时候才执行以下操作if(!isFling){//当前x的坐标upXint upX = (int) event.getX();//tempIndex临时下标int tempIndex = currIndex;if(upX - firstX > getWidth()/2){//滑动到上一页面tempIndex--;}else if(firstX - upX> getWidth()/2){//滑动到下一页面tempIndex++;}//让view的内容移动到子view上面去moveToDest(tempIndex);}break;}return true;}

6、让view的内容移动到子view上面去

public void moveToDest(int tempIndex) {//防止左右滑动超出边界if(tempIndex < 0){tempIndex = 0;}if(tempIndex > getChildCount()-1){tempIndex = getChildCount()-1;}//将临时的下标给当前子view的下标currIndex = tempIndex;/** * 触发改变页面的监听 */if(pageChangeListener != null){pageChangeListener.moveToDest(currIndex);}//scrollTo(currIndex*getWidth(), 0);//移动的距离  = 终点坐标 - 当前坐标int distanceX = currIndex*getWidth() - getScrollX();//开始执行动画scroller.startScroll(getScrollX(), getScrollY(), distanceX, 0,Math.abs(distanceX));/** * 刷新会导致computeScroll()的执行 */invalidate();}

@Overridepublic void computeScroll() {if(scroller.computeScrollOffset()){int curX = scroller.getCurrX();//获得当前滑动的位置//滑动到当前位置上scrollTo(curX, 0);//再刷新invalidate();}}

自定义MyScroller类(攻城狮)

/** * 用于计算速度和位移 * @author Administrator * */public class MyScroller {private int startX;private int startY;private int distanX;private int distanY;//开始执行动画的时间private long startTime;//判断动画是否执行完成private boolean isFished;public MyScroller(Context ctx){}/** * 开始滑动 * @param startX 基准点的x的坐标 * @param startY 基准点的y的坐标 * @param distanX x方向上要移动的距离 * @param distanY y方向上要移动的距离 */public void startScroll(int startX,int startY,int distanX,int distanY){this.startX = startX;this.startY = startY;this.distanX = distanX;this.distanY = distanY;//从手机开机到现在的毫秒数 但不包括手机休眠的时间this.startTime = SystemClock.uptimeMillis();//判断动画是否执行完成this.isFished = false;}/** * 系统默认运行动画执行的的总时间 3秒 */private int totalTime = 3000;private int currX;private int currY;public int getCurrX() {return currX;}public void setCurrX(int currX) {this.currX = currX;}public int getCurrY() {return currY;}public void setCurrY(int currY) {this.currY = currY;}/** * 计算当前的位移量 * @return  true 还在执行动画     false 动画已经结束 */public boolean computeScrollOffset(){//判断下动画是否已经结束----如果动画已经结束了就开始计算if(isFished){return false;}//获得已经运行的时间(当前时间-开始时间)long passTime = SystemClock.uptimeMillis()-startTime;//给它设置一个默认执行动画的总时间  来判断动画是否还在执行中  进行相应的计算if(passTime < totalTime){//说明动画还在执行当中//这段时间的位置 = 这段时间的差  * 速度//速度 = 总位移 / 总时间currX = (int)(startX + passTime*distanX/totalTime);currY = (int)(startY + passTime*distanY/totalTime);}else{//动画已经执行完成//计算当前x、y的位置currX = startX + distanX;currY = startY + distanY;//将isFished设置为true  表示动画已经执行完成了isFished = true;}return true;}}

7、在布局文件中RadioGroup,并在代码中动态添加RadioButton

<RadioGroup    android:id="@+id/my_radio_group"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:orientation="horizontal" ></RadioGroup>

//根据msv中的view的数量   动态添加RadioButton    for (int i = 0; i < msv.getChildCount(); i++) {        RadioButton button = new RadioButton(this);        //如果是第一个的位置就默认选中        if(i == 0){            button.setChecked(true);        }else{            button.setChecked(false);        }        //将button的下标值设置为他的id的值        button.setId(i);        //将button添加到radioGroup中        radioGroup.addView(button);    }

8、在MyScrollView中添加页面改变的监听

/** * 定义接口,当页面发生改变时  触发该事件 */interface IPageChangeListener{    void moveToDest(int destIndex);}private IPageChangeListener pageChangeListener;public IPageChangeListener getPageChangeListener() {    return pageChangeListener;}public void setPageChangeListener(IPageChangeListener pageChangeListener) {    this.pageChangeListener = pageChangeListener;}    //给msv设置改变监听    msv.setPageChangeListener(new MyScorllView.IPageChangeListener() {        @Override        public void moveToDest(int destIndex) {            //根据切换的页面下标  找到对应的值选中            //((RadioButton)radioGroup.getChildAt(destIndex)).setChecked(true);            radioGroup.check(destIndex);        }    });

9、给RadioButton添加改变时监听

//给RadioButton添加改变时监听    radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {        @Override        public void onCheckedChanged(RadioGroup group, int checkedId) {            //checkedId是选中的button的ID值            //在这里id是和下标一样  的数字            msv.moveToDest(checkedId);        }    });

10、添加测试页面test.xml(添加一个ScrollView滚动测试)这里用ScrollVIew模拟ListView

这里test.xml就不列出了  大家只需模拟在这个测试页面上有滑动点击事件即可想ScrollView和ListView都有滑动点击事件

/**     * 添加测试页面     */    View view = getLayoutInflater().inflate(R.layout.test, null);    msv.addView(view,2);    //根据msv中的view的数量   动态添加RadioButton    for (int i = 0; i < msv.getChildCount(); i++) {        RadioButton button = new RadioButton(this);        //如果是第一个的位置就默认选中        if(i == 0){            button.setChecked(true);        }else{            button.setChecked(false);        }        //将button的下标值设置为他的id的值        button.setId(i);        //将button添加到radioGroup中        radioGroup.addView(button);    }

在测量view的大小的时候,也需要测量子view的大小

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    //当ViewGroup在测量大小的时候  也应该为每个子view测量大小  否则就可能会出现问题    for (int i = 0; i < getChildCount(); i++) {        View view = getChildAt(i);        view.measure(widthMeasureSpec, heightMeasureSpec);    }}

11、给自定义ViewGroup添加事件分发

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    return super.dispatchTouchEvent(ev);}

12、自定义ViewGroup的事件中断机制

/** * 该方法默认返回false  即不中断事件的传递 * 如果返回true  即中断事件的传递   就需要我们自己来处理事件的传递  即执行onTouchEvent()方法 */@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    boolean result = false;    //如果水平滑动的距离大于竖直滑动的距离就中断竖直的子view滑动事件  否则就不中断    switch (ev.getAction()) {    case MotionEvent.ACTION_DOWN:        downInterX = (int) ev.getX();        downInterY = (int) ev.getY();        //解决detector不能收到down的事件  在这里将down的事件传递给它        //如果不将down时间传给它,那么onTouchEvent事件将不能正常执行        detector.onTouchEvent(ev);        firstX = lastX = (int) ev.getX();        //将是否正在快速滑动设置为false        isFling = false;        break;    case MotionEvent.ACTION_MOVE:        int distanceX = (int) Math.abs(ev.getX()-downInterX);        int distanceY = (int) Math.abs(ev.getY()-downInterY);        //为了防止手机抖动distanceX设置大于10        if(distanceX > distanceY && distanceX >10){            //水平移动大于竖直移动            result = true;        }else{            result = false;        }        break;    case MotionEvent.ACTION_UP:        break;    }    return result;};

好了 ,到这里,这个小demo就已经结束了,代码中都有详细的注释,大家看注释把,如果还是不理解的欢迎大家留言给我。。。。。

附上个人的一点难点理解:(这点是关于在测试页面点击事件触发的理解)

当点击view中的listview时,view先收到点击滑动的事件,再一层一层的往里面传递,最终传递到ListView上,而在此过程中外面层的事件,可随时消耗掉事件,这样外面层消耗了事件,里层就收不到滑动的事件,这样就被中断事件了。 当水平方向上的距离大于竖直方向上的距离时,竖直方向的滑动点击事件将被中断,也就是listview的事件被中断,这时就执行外层的滑动点击事件,反之,当滑动点击事件传递到里层的listview时,listview消耗了这个事件,这时外层的view滑动点击事件将被中断。 另外需要注意的就是,当滑动点击事件执行时,需要将down事件传递系统的手势滑动解析的工具GestureDetector,这样就可以防止滑动点击时跳动的问题。

最后给大家附上一张事件中断传递机制的图解,能更好的帮助大家理解后面的测试页面点击的部分。

Touch事件传递机制流程图

附上测试页面:

最后欢迎大家,关注我的博客,不定期分享一些有用的android方面的东西!



0 0
原创粉丝点击