android 网易item广告,Android仿网易严选商品详情页
仿照網(wǎng)易嚴(yán)選商品詳情頁面,整個(gè)頁面分為兩個(gè)部分,上面一部分是Native的ScrollView,下面一部分則是WebView,其目的是為了可以進(jìn)行分步加載。滑動(dòng)到ScrollView底部時(shí),繼續(xù)向上拖動(dòng),可以加載下面的WebView部分。反之,滑動(dòng)到WebView頂部時(shí),繼續(xù)向下拖動(dòng),可以展示上面的ScrollView部分。其中,在向上或者向下拖動(dòng)的時(shí)候,增加了一些阻力。另外,還使用了自定義控件輔助神器ViewDragHelper,可以使滑動(dòng)比較流暢。
一、自定義View
總體的實(shí)現(xiàn)思路是對ScrollView和WebView的dispatchTouchEvent 方法進(jìn)行重寫,當(dāng)在ScrollView的頂部并且向上拉,或者是在WebView的底部向下拉時(shí),自身不消費(fèi)事件,讓父容器攔截事件并處理,父容器Touch事件的攔截與處理都交給ViewDragHelper來處理。
1.自定義ViewGroup
public class GoodsDetailVerticalSlideView extends ViewGroup {
private static final int VEL_THRESHOLD = 6000;// 滑動(dòng)速度的閾值,超過這個(gè)絕對值認(rèn)為是上下
private int DISTANCE_THRESHOLD = 75;// 單位是dp,當(dāng)上下滑動(dòng)速度不夠時(shí),通過這個(gè)閾值來判定是應(yīng)該粘到頂部還是底部
private OnPullListener onPullListener;// 頁面上拉或者下拉監(jiān)聽器
private OnShowPreviousPageListener onShowPreviousPageListener;// 手指松開是否加載上一頁的監(jiān)聽器
private OnShowNextPageListener onShowNextPageListener; // 手指松開是否加載下一頁的監(jiān)聽器
private ViewDragHelper mDragHelper;
private GestureDetectorCompat mGestureDetector;// 手勢識(shí)別,處理手指在觸摸屏上的滑動(dòng)
private View view1;
private View view2;
private int viewHeight;
private int currentPage;// 當(dāng)前第幾頁
private int pageIndex;// 頁碼標(biāo)記
/**
* 設(shè)置頁面上拉或者下拉監(jiān)聽
* @param onPullListener
*/
public void setOnPullListener(OnPullListener onPullListener) {
this.onPullListener = onPullListener;
}
/**
* 設(shè)置加載上一頁監(jiān)聽
* @param onShowPreviousPageListener
*/
public void setOnShowPreviousPageListener(OnShowPreviousPageListener onShowPreviousPageListener) {
this.onShowPreviousPageListener = onShowPreviousPageListener;
}
/**
* 設(shè)置加載下一頁監(jiān)聽
* @param onShowNextPageListener
*/
public void setOnShowNextPageListener(OnShowNextPageListener onShowNextPageListener) {
this.onShowNextPageListener = onShowNextPageListener;
}
public GoodsDetailVerticalSlideView(Context context) {
this(context, null);
}
public GoodsDetailVerticalSlideView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GoodsDetailVerticalSlideView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
DISTANCE_THRESHOLD = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DISTANCE_THRESHOLD, getResources().getDisplayMetrics());
// 在自定義ViewGroup時(shí),ViewDragHelper可以用來拖拽和設(shè)置子View的位置(在ViewGroup范圍內(nèi))。
mDragHelper = ViewDragHelper.create(this, 10.0f, new DragCallBack());
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_BOTTOM);
mGestureDetector = new GestureDetectorCompat(getContext(), new YScrollDetector());
currentPage = 1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (view1 == null) view1 = getChildAt(0);
if (view2 == null) view2 = getChildAt(1);
//當(dāng)滑到第二頁時(shí),第二頁的top為0,第一頁為負(fù)數(shù)。
if (view1.getTop() == 0) {
view1.layout(0, 0, r, b);
view2.layout(0, 0, r, b);
viewHeight = view1.getMeasuredHeight();
view2.offsetTopAndBottom(viewHeight);// view2向下移動(dòng)到view1的底部
} else {
view1.layout(view1.getLeft(), view1.getTop(), view1.getRight(), view1.getBottom());
view2.layout(view2.getLeft(), view2.getTop(), view2.getRight(), view2.getBottom());
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
// Touch事件的攔截與處理都交給mDragHelper來處理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (view1.getBottom() > 0 && view1.getTop() < 0) {
// view粘到頂部或底部,正在動(dòng)畫中的時(shí)候,不處理Touch事件
return false;
}
boolean shouldIntercept = false;
boolean yScroll = mGestureDetector.onTouchEvent(ev);
try {
shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
//修復(fù)導(dǎo)致OnTouchEvent中pointerIndex out of range的異常
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mDragHelper.processTouchEvent(ev);
}
} catch (Exception e) {
e.printStackTrace();
}
return shouldIntercept && yScroll;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private class DragCallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
// 兩個(gè)子View都需要跟蹤,返回true
return true;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
// 由于拖拽導(dǎo)致被捕獲View的位置發(fā)生改變時(shí)進(jìn)行回調(diào)
if (changedView == view1) {
view2.offsetTopAndBottom(dy);
if (onPullListener != null && currentPage == 1) {
onPullListener.onPull(1, top);
}
}
if (changedView == view2) {
view1.offsetTopAndBottom(dy);
if (onPullListener != null && currentPage == 2) {
onPullListener.onPull(2, top);
}
}
// 如果不重繪,拖動(dòng)的時(shí)候,其他View會(huì)不顯示
ViewCompat.postInvalidateOnAnimation(GoodsDetailVerticalSlideView.this);
}
@Override
public int getViewVerticalDragRange(View child) {
// 這個(gè)用來控制拖拽過程中松手后,自動(dòng)滑行的速度
return child.getHeight();
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// 滑動(dòng)松開后,需要向上或者向下粘到特定的位置, 默認(rèn)是粘到最頂端
int finalTop = 0;
if (releasedChild == view1) {
// 拖動(dòng)view1松手
if (yvel < -VEL_THRESHOLD || releasedChild.getTop() < -DISTANCE_THRESHOLD) {
// 向上的速度足夠大或者向上滑動(dòng)的距離超過某個(gè)閾值,就滑動(dòng)到view2頂端
finalTop = -viewHeight;
}
} else {
// 拖動(dòng)view2松手
if (yvel > VEL_THRESHOLD || releasedChild.getTop() > DISTANCE_THRESHOLD) {
// 向下的速度足夠大或者向下滑動(dòng)的距離超過某個(gè)閾值,就滑動(dòng)到view1頂端
finalTop = viewHeight;
}
}
//觸發(fā)緩慢滾動(dòng)
//將給定子View平滑移動(dòng)到給定位置,會(huì)回調(diào)continueSettling(boolean)方法,在內(nèi)部是用的ScrollerCompat來實(shí)現(xiàn)滑動(dòng)的。
//如果返回true,表明動(dòng)畫應(yīng)該繼續(xù),所以調(diào)用者應(yīng)該調(diào)用continueSettling(boolean)在每個(gè)后續(xù)幀繼續(xù)動(dòng)作,直到它返回false。
if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {
ViewCompat.postInvalidateOnAnimation(GoodsDetailVerticalSlideView.this);
}
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
// 限制被拖動(dòng)的子View在垂直方向的移動(dòng),可以用作邊界約束
// 阻尼滑動(dòng),讓滑動(dòng)位移變?yōu)?/2,除數(shù)越大阻力越大
return child.getTop() + dy / 2;
}
}
@Override
public void computeScroll() {
// 判斷smoothSlideViewTo觸發(fā)的continueSettling(boolean)的返回值
if (mDragHelper.continueSettling(true)) {
// 如果當(dāng)前被捕獲的子View還需要繼續(xù)移動(dòng),則進(jìn)行重繪直到它返回false,返回false表示不用后續(xù)操作就能完成這個(gè)動(dòng)作了。
ViewCompat.postInvalidateOnAnimation(this);
if (view2.getTop() == 0) {
currentPage = 2;
if (onShowNextPageListener != null && pageIndex != 2) {
onShowNextPageListener.onShowNextPage();
pageIndex =2;
}
} else if (view1.getTop() == 0) {
currentPage = 1;
if (onShowPreviousPageListener != null && pageIndex != 1) {
onShowPreviousPageListener.onShowPreviousPage();
pageIndex =1;
}
}
}
}
/** 滾動(dòng)到view1頂部 */
public void smoothSlideToFirstPageTop() {
if (currentPage == 2) {
//觸發(fā)緩慢滾動(dòng)
if (mDragHelper.smoothSlideViewTo(view2, 0, viewHeight)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
/** 滾動(dòng)到view2頂部 */
public void smoothSlideToSecondPageTop() {
if (currentPage == 1) {
//觸發(fā)緩慢滾動(dòng)
if (mDragHelper.smoothSlideViewTo(view1, 0, -viewHeight)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
private class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
// 垂直滑動(dòng)時(shí)dy>dx,才被認(rèn)定是上下拖動(dòng)
return Math.abs(dy) > Math.abs(dx);
}
}
public interface OnPullListener{
void onPull(int currentPage, int top);
}
public interface OnShowPreviousPageListener{
void onShowPreviousPage();
}
public interface OnShowNextPageListener {
void onShowNextPage();
}
}
其中,有一些回調(diào)監(jiān)聽Listener,在具體業(yè)務(wù)邏輯處理的時(shí)候,可以跟Activity進(jìn)行相應(yīng)的交互,其余部分基本都有代碼注釋了。
2.自定義ScrollView
public class GoodsDetailScrollView extends ScrollView {
private float downX;
private float downY;
public GoodsDetailScrollView(Context context) {
this(context, null);
}
public GoodsDetailScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GoodsDetailScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
//如果滑動(dòng)到了最底部,就允許繼續(xù)向上滑動(dòng)加載下一頁,否者不允許
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - downX;
float dy = ev.getY() - downY;
boolean allowParentTouchEvent;
if (Math.abs(dy) > Math.abs(dx)) {
if (dy > 0) {
//ScrollView頂部下拉時(shí)需要放大圖片,自身消費(fèi)事件
allowParentTouchEvent = false;
} else {
//位于底部時(shí)上拉,讓父View消費(fèi)事件
allowParentTouchEvent = isBottom();
}
} else {
//水平方向滑動(dòng),自身消費(fèi)事件
allowParentTouchEvent = false;
}
getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent);
}
return super.dispatchTouchEvent(ev);
}
public boolean isTop() {
return !canScrollVertically(-1);
}
public boolean isBottom() {
return !canScrollVertically(1);
}
public void goTop() {
scrollTo(0, 0);
}
}
其中,可以根據(jù)自身業(yè)務(wù)邏輯的需要,對dispatchTouchEvent事件分發(fā)做相應(yīng)的調(diào)整。
3.自定義WebView
public class GoodsDetailWebView extends WebView {
private float downX;
private float downY;
public GoodsDetailWebView(Context context) {
this(context, null);
}
public GoodsDetailWebView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GoodsDetailWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getX();
downY = ev.getY();
//如果滑動(dòng)到了最頂部,就允許繼續(xù)向下滑動(dòng)加載上一頁,否者不允許
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - downX;
float dy = ev.getY() - downY;
boolean allowParentTouchEvent;
if (Math.abs(dy) > Math.abs(dx)) {
if (dy > 0) {
//位于頂部時(shí)下拉,讓父View消費(fèi)事件
allowParentTouchEvent = isTop();
} else {
//向上滑動(dòng),自身消費(fèi)事件
allowParentTouchEvent = false;
}
} else {
//水平方向滑動(dòng),自身消費(fèi)事件
allowParentTouchEvent = false;
}
getParent().requestDisallowInterceptTouchEvent(!allowParentTouchEvent);
}
return super.dispatchTouchEvent(ev);
}
public boolean isTop() {
return getScrollY() <= 0;
}
public boolean isBottom() {
return getHeight() + getScrollY() >= getContentHeight() * getScale();
}
public void goTop() {
scrollTo(0, 0);
}
}
同樣的,可以根據(jù)自身業(yè)務(wù)邏輯的需要,對dispatchTouchEvent事件分發(fā)做相應(yīng)的調(diào)整。
二、如何使用
1.在Activity中使用GoodsDetailVerticalSlideView控件
(1)使用GoodsDetailVerticalSlideView控件,內(nèi)部包含兩個(gè)子View,分別表示第一部分ScrollView和第二部分WebView,可以先使用FrameLayout占位,然后在代碼中使用Fragment替換。
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
android:id="@+id/goods_detail_vertical_slide_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/layout_goods_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:id="@+id/layout_goods_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
(2)當(dāng)然,也可以直接使用GoodsDetailScrollView和GoodsDetailWebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
android:id="@+id/goods_detail_vertical_slide_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent">
......
android:layout_width="match_parent"
android:layout_height="match_parent">
......
2.在Fragment中使用GoodsDetailScrollView和GoodsDetailWebView
這邊有個(gè)注意點(diǎn)就是上拉或者下拉的時(shí)候,我們一般都會(huì)給用戶展示一個(gè)文字和圖片的指示器來提示用戶如何操作,我們只需要把指示器放在上面一部分的ScrollView布局里面即可,然后根據(jù)目前正在展示哪一部分進(jìn)行顯示/隱藏以及文字圖片變化就可以了,這樣可以使我們的整個(gè)拖動(dòng)效果看起來比較流暢。
(1)Fragment中使用GoodsDetailScrollView
android:id="@+id/goods_detail_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
......
總結(jié)
以上是生活随笔為你收集整理的android 网易item广告,Android仿网易严选商品详情页的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 宜春看输卵管炎最好的医院推荐
- 下一篇: 《从临海王上荆初发新渚诗》第十七句是什么