仿造淘宝上拉加载详情控件

仿造淘宝上拉加载详情控件

控件结构如下

这里写图片描述

逻辑处理

父控件Layout在静止的时候可能的滚动距离只有2种情况

  • 显示topScrollView,滚动情况为未滚动的(0,0)位置

  • 显示bottomScrollView时滚动为(0,mHeight)

子scrollView滚动情况

  • 子view处于可滚动状态时不做处理。当topScrollView滚动到底部或者bottomScrollView滚动到顶部时,此时再向上(或者向下)拖动时,就应该滚动父Layout了。这其中父控件跟随手指滑动的方法有很多,本文采用的是view自带的scrollBy()方法,在dispatchTouchEvent()方法的Move函数中获取手指每次移动的距离,并且滚动父视图。

  • 手指释放时父视图回滚的操作,这里配合前面的scroll来用Scroller来处理,它自带平滑滚动的效果,只要知道开始位置和结束位置(或者说滚动距离),就可以平滑的滚动到目标位置,而目标位置如上2所提到的2种情况。

代码部分

为了监听scrollView手指fling的时候的滑动状况,自定义了一个scrollView 值修改了onScrollChanged()方法加入了一个监听

public class FlingScrollView extends ScrollView {

    private OnScrollListener mListener;

    public void setOnScrollListener(OnScrollListener listener) {
        this.mListener = listener;
    }

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

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

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

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mListener != null) {
            mListener.onScrollAndFling(this, t);
        }
    }

    public interface OnScrollListener {
        void onScrollAndFling(ViewGroup v, int scrollY);
    }

}

控件整体代码

/**
 * 仿淘宝上拉查看详情模块
 * Created by lqh on 2016/5/8.
 */
public class ScrollContainerView extends RelativeLayout {
    private static final int SPEED = 500;
    //mCurIndex = 0,当scrollY > mHeight*1/4f时,自动滚动到下一页
    //mCurIndex = 1,当scrollY < mHeight*3/4f时,自动滚动到上一页
    private static final float AUTO_SCROLL = 1 / 4f;
    private int mWidth, mHeight;
    private ViewGroup mBottomView;
    private FlingScrollView mTopScrollView;
    private FlingScrollView mBottomScrollView;
    private int mAutoUpScrollY;

    private int mTopScrollSize;//topView滚动到底部时的滚动距离

    private boolean canPullUp, canPullDown;
    //* 1.标记自动回滚的mScroller操作
    //* 2.标记正在滚动
    private boolean mIsScroller = false;

    private int mCurIndex = 0;
    //该值用于限制从子视图滚动过渡到父视图滚动,需要在子视图滚动完毕后父视图处于可滚动状态时,再次按下才能拖拽
    private int mEvent = 0;

    private int mScrollSize;

    private int mLastY;

    private Scroller mScroller;
    private boolean mIsViewChange;
    private boolean mShouldChangeView;

    private VelocityTracker mTracker;

    private ViewChangeListener mViewChangeListener;


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

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

    public ScrollContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mScroller = new Scroller(getContext());
        mTracker = VelocityTracker.obtain();
    }

    public void setViewChangeListener(ViewChangeListener listener) {
        this.mViewChangeListener = listener;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mTopScrollView = (FlingScrollView) getChildAt(0);

        mBottomView = (ViewGroup) getChildAt(1);
        if (mBottomView instanceof ScrollView)
            mBottomScrollView = (FlingScrollView) mBottomView;
        else
            mBottomScrollView = (FlingScrollView) mBottomView.getChildAt(1);

        mTopScrollView.setOnScrollListener(mTopScrollListener);
        mBottomScrollView.setOnScrollListener(mBottomScrollListener);
    }

    private FlingScrollView.OnScrollListener mTopScrollListener = new FlingScrollView.OnScrollListener() {
        @Override
        public void onScrollAndFling(ViewGroup v, int scrollY) {
            if (mTopScrollSize == 0)
                mTopScrollSize = mTopScrollView.getChildAt(0).getMeasuredHeight() - mHeight;
            canPullUp = scrollY == mTopScrollSize && mCurIndex == 0;
        }
    };


    private FlingScrollView.OnScrollListener mBottomScrollListener = new FlingScrollView.OnScrollListener() {
        @Override
        public void onScrollAndFling(ViewGroup v, int scrollY) {
            canPullDown = scrollY == 0 && mCurIndex == 1;
        }
    };

    /**
     * 布局两个子View 高度必须设置match_parent,他们的高度和父控件是一致的
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        mAutoUpScrollY = (int) (AUTO_SCROLL * h);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mTopScrollView.layout(0, 0, mWidth, mHeight);
        mBottomView.layout(0, mHeight, mWidth, 2 * mHeight);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //如果当前视图正在滚动,则屏蔽所有对该视图的操作
        if (mIsScroller)
            return true;

        int y = (int) ev.getY(0);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mTracker.clear();
                mTracker.addMovement(ev);
                mEvent = 0;
                mLastY = y;
                //获取当前父视图滚动的距离,显示第一页时为0,第二页时为mHeight
                mScrollSize = mCurIndex * mHeight;

                break;
            case MotionEvent.ACTION_MOVE:
                mTracker.addMovement(ev);
                int distance = y - mLastY;
                shouldScroll(distance);
                mLastY = y;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mTracker.addMovement(ev);
                mTracker.computeCurrentVelocity(1000);
                autoScroll();
                break;
        }

        //父视图处于静止状态时,传递事件给子View;当父视图处于拖拽状态时,不传递事件给子View
        if (getScrollY() == 0 || getScrollY() == mHeight) {
            super.dispatchTouchEvent(ev);
        }

        return true;
    }

    /**
     * 是否滚动父视图
     *
     * @param distance 手指move的距离
     */

    private void shouldScroll(int distance) {
        if (canPullUp && mEvent == 0 && mCurIndex == 0) {
            //当滑动不是单一方向时,即先上拉后又下拉,则判定为不需要更改视图
            mShouldChangeView = distance <= 0;
            //为实现拖拽效果,此处父视图滚动的距离为手指滑动距离的1/2
            mScrollSize -= distance / 2;
            if (mScrollSize < 0)
                mScrollSize = 0;
            if (mScrollSize > mHeight / 2)
                mScrollSize = mHeight / 2;
            scrollTo(0, mScrollSize);
            canPullDown = true;
            return;
        }
        if (canPullDown && mEvent == 0 && mCurIndex == 1) {
            mShouldChangeView = distance >= 0;
            mScrollSize -= distance / 2;
            if (mScrollSize > mHeight)
                mScrollSize = mHeight;
            if (mScrollSize < mHeight / 2)
                mScrollSize = mHeight / 2;
            scrollTo(0, mScrollSize);
            canPullUp = true;
            return;
        }
        //若此时为子视图在滚动,则event设置为-1,使得它即使满足canPullUp也无法进入到拖拽父视图操作,
        // 只有当再次down事件才可以拖拽
        mEvent = -1;
    }

    /**
     * 手指离开后处理视图复位,
     */
    private void autoScroll() {
        //获取手指释放时父视图滚动的距离,依此来判断接下来要回滚的位置
        final int scrollY = getScrollY();
        final int ySpeed = (int) Math.abs(mTracker.getYVelocity());
        if (scrollY == 0 || scrollY == mHeight)
            return;

        if (scrollY <= 10) {
            scrollTo(0, 0);
            return;
        }

        if (scrollY >= mHeight - 10) {
            scrollTo(0, mHeight);
            return;
        }

        if (!mShouldChangeView) {
            scrollBack(scrollY);
            return;
        }
        if (ySpeed < SPEED) {
            // if (mCurIndex == 0 && scrollY > mAutoUpScrollY)和
            // if (mCurIndex == 1 && scrollY < mHeight - mAutoUpScrollY)的合并条件语句
            if ((2 * mCurIndex - 1) * scrollY <= mCurIndex * mHeight - mAutoUpScrollY)
                scrollNext(scrollY);
            else
                scrollBack(scrollY);

        } else {
            scrollNext(scrollY);
        }
    }

    private void scrollNext(int scrollY) {
        mCurIndex = 1 - mCurIndex;
        mIsViewChange = true;
        mScroller.startScroll(0, scrollY, 0, mCurIndex * mHeight - scrollY, 500);
        invalidate();
    }

    private void scrollBack(int scrollY) {
        mIsViewChange = false;
        mScroller.startScroll(0, scrollY, 0, mCurIndex * mHeight - scrollY, 500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            if (!mIsScroller)
                mIsScroller = true;
            scrollTo(0, mScroller.getCurrY());
            if (mViewChangeListener != null)
                mViewChangeListener.scrollChanged(mScroller.getCurrY());

            postInvalidate();
        } else {
            if (mIsScroller) {
                if (mIsViewChange && mViewChangeListener != null) {
                    mViewChangeListener.showView(mCurIndex);
                }
                mIsViewChange = false;
                mIsScroller = false;
            }
        }
    }

    /**
     * 页面切换的监听
     */
    public interface ViewChangeListener {
        void showView(int curViewIndex);

        void scrollChanged(int scrollSize);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值