仿淘宝ViewPager左滑加载详情界面

先看效果:

实现步骤:

外层用ViewPager,最后一个界面不是一般的控件,用到自定义布局

自定义布局中包含两个界面:最后一张图片,及左滑显示的显示,松手后,界面得回到原来的

具体代码:

package qf.com.slideviewgroup;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2017/1/1 0001.
 */
public class SlideViewGroup extends LinearLayout implements View.OnTouchListener {
    static final int MODE_LEFT = 1;
    static final int MODE_RIGHT = 2;
    static final int MODE_LEFT_RIGHT = 3;


    /**
     * 默认模式
     */
    int mode = MODE_RIGHT;

    static final String TAG = "ytmfdw";

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return super.onTouchEvent(event);
    }


    public interface CallBack {
        public boolean leftSliding();

        public boolean rightSliding();
    }

    SlideViewGroup.CallBack callback;


    private List<View> views = new ArrayList<>();

    /**
     * 防止没触发clampViewPositionHorizontal滑动事件
     */
    boolean isSlide = false;

    /**
     * 能否回调
     */
    boolean canCallBack = false;

    /**
     * 当前移动的控件序号
     */
    int indexView = 0;

    /**
     * 开始显示的界面
     */
    int startView = 0;

    /**
     * 结束界面
     */
    int endView = 0;


    /**
     * 向左还是向右滑动,left>0向右,left<0向左
     */
    private boolean left = true;

    private ViewDragHelper dragHelper;

    //动画
    private ObjectAnimator anim = null;

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

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

    public SlideViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initViews();
    }

    public void setSlideLisgener(SlideViewGroup.CallBack cb) {
        this.callback = cb;
    }

    private void initViews() {
        //水平方向
        setOrientation(HORIZONTAL);
        dragHelper = ViewDragHelper.create(this, 0.2f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                L("tryCaptureView~~~~~~~");
                indexView = views.indexOf(child);
                L("indexView=" + indexView);

                if (anim != null && anim.isRunning()) {
                    anim.cancel();
                    anim = null;
                }
                return true;
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
//                L("clampViewPoistionHorizontal::::left=" + left + "dx=" + dx);
                isSlide = true;
                canCallBack = false;
                if (mode != MODE_LEFT) {
                    if (indexView == startView) {
                        return left > 0 ? 0 : dx;
                    }
                }
                return dx;
            }

            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
//                L("onViewPositionChanged=" + left);
                if (!isSlide) {
                    return;
                }
                LinearLayout.LayoutParams params = (LayoutParams) views.get(0).getLayoutParams();
                params.leftMargin += left;
                views.get(0).setLayoutParams(params);
                isSlide = false;
            }

            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
//                L("onViewREleased~~~~~~~~~~indexView=" + indexView);
                //可以再次加了
                int start = getL();
                int end = 0;
                for (int i = 0; i < views.size(); i++) {
                    if (i == indexView) {
                        break;
                    }
                    end += views.get(i).getMeasuredWidth();
                }

                if (callback != null) {
                    if ((mode == MODE_LEFT || mode == MODE_LEFT_RIGHT) &&
                            canCallBack && indexView == startView) {
                        if (callback.leftSliding()) {
                            return;
                        }
                    } else if (mode >= MODE_RIGHT && canCallBack && indexView == endView) {
                        if (callback.rightSliding()) {
                            return;
                        }
                    }
                }

                L("start=" + start + "end=" + end);

                //创建动画
                anim = ObjectAnimator.ofInt(SlideViewGroup.this, "L", start, -end);
                anim.setDuration(300);
                anim.setInterpolator(new DecelerateInterpolator());
                anim.start();
            }

            //解决点击与滑动事件冲突
            @Override
            public int getViewHorizontalDragRange(View child) {
                return child.isClickable() ? child.getWidth() : 0;
            }

            @Override
            public int getViewVerticalDragRange(View child) {
                return child.isClickable() ? child.getHeight() : 0;
            }

        });
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //解决子控件的宽度为match_parent
        for (View view : views) {
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            int childW = params.width;
            if (childW == -1) {
                //match_parent
                params.width = this.getMeasuredWidth();
                view.setLayoutParams(params);
            }
        }
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            views.add(getChildAt(i));
            getChildAt(i).setOnTouchListener(this);
        }

        endView = count - 2;
    }


    boolean intercept = false;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                start = ev.getX();
            }
            break;
            case MotionEvent.ACTION_MOVE: {
                if (indexView >= 0 && indexView < views.size()) {
                    View view = views.get(indexView);
                    L(view.getClass().getName());
                    float dx = ev.getX() - start;
                    if (dx > 0) {
                        if (indexView >= startView && mode == MODE_LEFT) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }


                        //向右滑动
                        boolean canScroll = canChildScrollRight(view);
                        L("向右滑动:" + canScroll);
                        if (!canScroll) {
                            L("向右滑动:拦截");
                            intercept = true;
                            return intercept;
                        } else {
                            return dragHelper.shouldInterceptTouchEvent(ev);
                        }

                    } else {
                        if (indexView <= endView) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                        //向左滑动
                        boolean canScroll = canChildScrollLeft(view);
                        L("向左滑动:" + canScroll);
                        if (!canScroll) {
                            L("向左滑动:拦截");
                            intercept = true;
                            return intercept;
                        } else {
                            return dragHelper.shouldInterceptTouchEvent(ev);
                        }
                    }
                }
            }
            break;
            case MotionEvent.ACTION_UP: {
                if (intercept) {
                    return true;
                }
            }
            break;
        }

        return dragHelper.shouldInterceptTouchEvent(ev);
    }

    float start = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                //这个方法不会被回调
                start = event.getX();
            }
            break;
            case MotionEvent.ACTION_UP: {
                float dx = event.getX() - start;
                L("onTouchEvent up=" + dx);
                int w = views.get(indexView).getMeasuredWidth();
                if (dx >= w / 2) {
                    //向右滑动--
                    indexView--;
                    if (indexView < startView) {
                        indexView = startView;
                        //防止非最后一个被拖动时触发回调
                        canCallBack = true;
                    }

                } else if (dx <= -w / 2) {
                    //向左滑动,++
                    indexView++;
                    if (indexView > endView) {
                        indexView = endView;
                        //防止非最后一个被拖动时触发回调
                        canCallBack = true;
                    }
                }
            }
            break;
        }
        dragHelper.processTouchEvent(event);
        return true;
    }


    public void setL(int left) {
        LinearLayout.LayoutParams params = (LayoutParams) views.get(0).getLayoutParams();
        params.leftMargin = left;
        views.get(0).setLayoutParams(params);
    }

    public int getL() {
        LinearLayout.LayoutParams params = (LayoutParams) views.get(0).getLayoutParams();
        return params.leftMargin;
    }


    public static void L(String msg) {
        Log.d(TAG, msg);
    }


    /**
     * 判断子控件是否可以左滑
     *
     * @param view
     * @return
     */
    public static boolean canChildScrollLeft(View view) {
        L("canChildScrollLeft class =" + view.getClass().getName());
      /*  if (view instanceof ViewPager) {
            final ViewPager viewPager = (ViewPager) view;
            return viewPager.getCurrentItem() != 0;
        } else {*/
           /* if (android.os.Build.VERSION.SDK_INT < 14) {

                return view.getScrollX() < 0;

            } else {
                return view.canScrollHorizontally(1);
            }*/

        return view.canScrollHorizontally(1);
//        }
    }

    /**
     * 判断子控件是否可以左滑
     *
     * @param view
     * @return
     */
    public static boolean canChildScrollRight(View view) {
        L("canChildScrollRight class =" + view.getClass().getName());
     /*   if (view instanceof ViewPager) {
            final ViewPager viewPager = (ViewPager) view;
            return viewPager.getCurrentItem() != viewPager.getAdapter().getCount() - 1;
        } else {*/
            /*if (android.os.Build.VERSION.SDK_INT < 14) {

                return view.getScrollX() > 0;

            } else {
                return view.canScrollHorizontally(-1);
            }*/
        return view.canScrollHorizontally(-1);
//        }
    }
}

需要的技术:

1、ViewDragHelper类,实现布局中的子控件拖动事件

2、属性动画,当手指离开屏幕时,得恢复到某个界面

3、TouchEvent拦截机制


ViewDragHelper解决子控件拖动事件:

在初始化ViewDragHelper中,写个CallBack(注意:这不是接口,是一个抽象类)

相关方法:

clampViewPositionHorizontal                水平方向移动距离,该方法返回的值,给onViewPositionChanged使用

onViewPositionChanged                       子控件移动时的位置改变,在这个方法中,设置第一个子控件的margin_left值,就可以达到水平方向滚动效果(前提是该类继承自LinearLayout)

onViewReleased                                      当手指离开界面时回调,在这个方法中,要判断是否达到触发左右滑动事件(即在最后一个界面时,再向左滑动一半,触发左滑事件),并启动属性动画,让界面恢复到显示某个界面

getViewHorizontalDragRange                解决点击事件冲突


属性动画:

写一个setL和getL的方法,这个方法就是用来设置和获取第一个子控件的margin_left值

然后就可以利用ObjectAnimtor.ofInt(this,"L",startValue,endValue)来创建属性动画了


TouchEvent拦截:

在onInterceptTouchEvent方法中,如果当前正在移动,如果移动方向还是向左的(利用按下时,得到起点x值,再用当前值-起点值,如果<0,左,如果>0右),但当前被拖动的直接子控件不是第一个子控件,就让父控件不要拦截这个事件:

float dx = ev.getX() - start;
if (dx > 0) {
if (indexView >= startView && mode == MODE_LEFT) {
getParent().requestDisallowInterceptTouchEvent(true);
}

向右方向同理


难解决的是事件冲突,这个自定义布局可包含多个直接子控件,在该布局下,停止状态时,同一时刻只能显示一个直接子控件,滑动时可像ViewPager一样

即该布局可左右滑动,ViewPager也可左右滑动,


源码下载:请点击我


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ytmfdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值