模倣淘宝網、Jingdongは、製品のドラッグを達成するために

アプリケーション電気の供給、製品のリストは間違いない非常に重要なモジュール、主流のショッピングアプリ詳細画面の観察のために、それらのほとんどがされて、次のH5商品の詳細二つの部分、上記商品仕様の表示、で作られた、または埋め込まれましたH5は細部のセットとコメントViewPagerインターフェイスのリストが含まれ、この論文では、最大と粘性[表示] -のさまざまなニーズダウン互換性のスクロールを実現することであるDragScrollDetailsLayout

GitHubのリンクDragScrollDetailsLayout

レンダリングを実現

実装のレンダリングを初めて目

シンプルScrollView +のWebView

もちろん、あなたはまた、サポートのような他のListViewへのWebViewを交換する場合。

ScrollView + ViewPager

アプリケーションシナリオ:複数のインターフェースは、底部に追加する必要があり、スライドが必要

ScrollView + Fragmenttabhost

アプリケーションシナリオ:複数のインターフェースは、底部に追加する必要はなく、スライドを必要としません

実現

このシーンのニーズに合わせて、それが実装する2つの部分に分けることができると考えるのは簡単です、唯一の垂直のLinearLayoutを必要とし、残りの部分は、カスタムインテリアのViewGroupは、最初の2つのトップのサブViewmUpstairsView、ビューmDownstairsViewを宣言するために、スクロールやアニメーションの問題に対処することですそして、変数のマークは、現在ビューをCurrentTargetIndex運転されている使用しています

    public class DragScrollDetailsLayout extends LinearLayout {
        private View mUpstairsView;
        private View mDownstairsView;
        private View mCurrentTargetView;

        public enum CurrentTargetIndex {
            UPSTAIRS,
            DOWNSTAIRS;

            public static CurrentTargetIndex valueOf(int index) {
                return 1 == index ? DOWNSTAIRS : UPSTAIRS;
            }
        }

そして、スクロールイベントに焦点を当て、スクロールやアニメーションのために解決すべき次の主要な問題を抱えています:

  • どのように私は、上部または下部にスクロールした上または下に表示する知っています
  • あなたが国境までスクロールすると、スライド傍受に対処する方法
  • 可動ハンドルその後の効果を手放す方法

スクロールの境界線を確認する方法

首先来看第一个问题,如何知道上面或者下面的View滚动到了边界,其实Android源码中有个类ViewCompat,它有个函数canScrollVertically(View view, int offSet, MotionEvent ev)就可以判断当前View是否可以向哪个方向滚动,offset的正负值用来判断向上还是向下,当然,仅仅靠这个函数还是不够的,因为ViewGroup是可以相互嵌套的,也许ViewGroup本身不能滚动,但是其内部的子View却可以滚动,这时候,就需要递归遍历相关的View,比如对于ViewPager中嵌套了包含WebView或者List的Fragment。不过,并非所有的子View都需要遍历,只有与TouchEvent相关的View才需要判断。因此还需要写个函数判断View是否在TouchEvent所在的区域,如下函数isTransformedTouchPointInView:

/***
 * 判断MotionEvent是否处于View上面
 */
protected boolean isTransformedTouchPointInView(MotionEvent ev, View view) {
    float x = ev.getRawX();
    float y = ev.getRawY();
    int[] rect = new int[2];
    view.getLocationInWindow(rect);
    float localX = x - rect[0];
    float localY = y - rect[1];
    return localX >= 0 && localX < (view.getRight() - view.getLeft())
            && localY >= 0 && localY < (view.getBottom() - view.getTop());
}

之后我们可以利用该函数对View进行递归遍历,判断最上层的ViewGroup是否可以上下滑动

    private boolean canScrollVertically(View view, int offSet, MotionEvent ev) {

        if (!mChildHasScrolled && !isTransformedTouchPointInView(ev, view)) {
            return false;
        }
        if (ViewCompat.canScrollVertically(view, offSet)) {
            mChildHasScrolled = true;
            return true;
        }
        if (view instanceof ViewPager) {
            return canViewPagerScrollVertically((ViewPager) view, offSet, ev);
        }
        if (view instanceof ViewGroup) {
            ViewGroup vGroup = (ViewGroup) view;
            for (int i = 0; i < vGroup.getChildCount(); i++) {
                if (canScrollVertically(vGroup.getChildAt(i), offSet, ev)) {
                    mChildHasScrolled = true;
                    return true;
                }
            }
        }
        return false;
    }

知道View是否可以上下滑动到边界后,拦截事件的时机就比较清晰了,那么接着看第二个问题,如何拦截滑动。

事件拦截处理

onInterceptTouchEvent在返回True之后,就不会再执行了,我们只需要把握准确的拦截时机,比如如果处于上面的View,就要对上拉事件比较敏感,处于底部就要对下拉事件敏感,同时还要将无效的手势归零,比如,操作上面的View时,如果先是下拉,并且是无效的下拉,那么就要将拦截点重置。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            mDownMotionX = ev.getX();
            mDownMotionY = ev.getY();
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.clear();
            mChildHasScrolled=false;
            break;
        case MotionEvent.ACTION_MOVE:
            adjustValidDownPoint(ev);
            return checkCanInterceptTouchEvent(ev);
        default:
            break;
    }
    return false;
}

checkCanInterceptTouchEvent主要用来判断是否需要拦截,并非不可滚动,就需要拦截事件,不可滚动只是一个必要条件而已,

   private boolean checkCanInterceptTouchEvent(MotionEvent ev) {
   final float xDiff = ev.getX() - mDownMotionX;
   final float yDiff = ev.getY() - mDownMotionY;
   if (!canChildScrollVertically((int) yDiff,ev)) {
       mInitialInterceptY = (int) ev.getY();
       if (Math.abs(yDiff) > mTouchSlop && Math.abs(yDiff) >= Math.abs(xDiff)
               && !(mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS && yDiff > 0
               || mCurrentViewIndex == CurrentTargetIndex.DOWNSTAIRS && yDiff < 0)) {
           return true;
       }
   }
   return false;
}   

事件拦截之后,就是对Move事件进行处理

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            flingToFinishScroll();
            recycleVelocityTracker();
            break;
        case MotionEvent.ACTION_MOVE:
            scroll(ev);
            break;
        default:
            break;
    }
    return true;
}

滚动比较简单,直接调用scrollTo就可以,同时为了收集滚动速度,还可以用VelocityTracker做一下记录:

private void scroll(MotionEvent event) {
    if (mCurrentViewIndex == CurrentTargetIndex.UPSTAIRS) {
        if (getScrollY() <= 0 && event.getY() > mInitialInterceptY) {
            mInitialInterceptY = (int) event.getY();
        }
        scrollTo(0, (int) (mInitialInterceptY - event.getY()));
    } else {
        if (getScrollY() >= mUpstairsView.getMeasuredHeight() && event.getY() < mInitialInterceptY) {
            mInitialInterceptY = (int) event.getY();
        }
        scrollTo(0, (int) (mInitialInterceptY - event.getY() + mUpstairsView.getMeasuredHeight()));
    }
    mVelocityTracker.addMovement(event);
}    

收尾动画

在Up事件之后,还要简单的处理一下一下收尾的滚动动画,比如,滚动距离不够要复原,否则,就滚动到目标视图,这里主要是根据Up事件的位置,计算需要滚动的距离,并通过Scroller来完成剩下的滚动。

private void flingToFinishScroll() {

    final int pHeight = mUpstairsView.getMeasuredHeight();
    final int threshold = (int) (pHeight * mPercent);
    float scrollY = getScrollY();
    if (CurrentTargetIndex.UPSTAIRS == mCurrentViewIndex) {
        if (scrollY <= 0) {
            scrollY = 0;
        } else if (scrollY <= threshold) {
            if (needFlingToToggleView()) {
                scrollY = pHeight - getScrollY();
                mCurrentViewIndex = CurrentTargetIndex.DOWNSTAIRS;
            } else
                scrollY = -getScrollY();
        } else {
            scrollY = pHeight - getScrollY();
            mCurrentViewIndex = CurrentTargetIndex.DOWNSTAIRS;
        }
    } else if (CurrentTargetIndex.DOWNSTAIRS == mCurrentViewIndex) {
        if (pHeight - scrollY <= threshold) {
            if (needFlingToToggleView()) {
                scrollY = -getScrollY();
                mCurrentViewIndex = CurrentTargetIndex.UPSTAIRS;
            } else
                scrollY = pHeight - scrollY;
        } else if (scrollY < pHeight) {
            scrollY = -getScrollY();
            mCurrentViewIndex = CurrentTargetIndex.UPSTAIRS;
        }
    }
    mScroller.startScroll(0, getScrollY(), 0, (int) scrollY, mDuration);
    if (mOnSlideDetailsListener != null) {
        mOnSlideDetailsListener.onStatueChanged(mCurrentViewIndex);
    }
    postInvalidate();
}

以上就是常用商品详情黏滞布局的实现。

自己是从事了七年开发的Android工程师,不少人私下问我,2019年Android进阶该怎么学,方法有没有?

没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升Android开发,却又不知道怎么进阶学习的朋友。【包括高级UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

资料获取方式:加入Android架构交流QQ群聊:513088520 ,进群即领取资料!!!

点击链接加入群聊【Android移动架构总群】:加入群聊

资料大全

おすすめ

転載: blog.csdn.net/weixin_45136073/article/details/92837221