android 網易item廣告,Android仿網易嚴選商品詳情頁

仿照網易嚴選商品詳情頁面,整個頁面分為兩個部分,上面一部分是Native的ScrollView,下面一部分則是WebView,其目的是為了可以進行分步加載。滑動到ScrollView底部時,繼續向上拖動,可以加載下面的WebView部分。反之,滑動到WebView頂部時,繼續向下拖動,可以展示上面的ScrollView部分。其中,在向上或者向下拖動的時候,增加了一些阻力。另外,還使用了自定義控件輔助神器ViewDragHelper,可以使滑動比較流暢。

一、自定義View

總體的實現思路是對ScrollView和WebView的dispatchTouchEvent 方法進行重寫,當在ScrollView的頂部并且向上拉,或者是在WebView的底部向下拉時,自身不消費事件,讓父容器攔截事件并處理,父容器Touch事件的攔截與處理都交給ViewDragHelper來處理。

1.自定義ViewGroup

public class GoodsDetailVerticalSlideView extends ViewGroup {

private static final int VEL_THRESHOLD = 6000;// 滑動速度的閾值,超過這個絕對值認為是上下

private int DISTANCE_THRESHOLD = 75;// 單位是dp,當上下滑動速度不夠時,通過這個閾值來判定是應該粘到頂部還是底部

private OnPullListener onPullListener;// 頁面上拉或者下拉監聽器

private OnShowPreviousPageListener onShowPreviousPageListener;// 手指松開是否加載上一頁的監聽器

private OnShowNextPageListener onShowNextPageListener; // 手指松開是否加載下一頁的監聽器

private ViewDragHelper mDragHelper;

private GestureDetectorCompat mGestureDetector;// 手勢識別,處理手指在觸摸屏上的滑動

private View view1;

private View view2;

private int viewHeight;

private int currentPage;// 當前第幾頁

private int pageIndex;// 頁碼標記

/**

* 設置頁面上拉或者下拉監聽

* @param onPullListener

*/

public void setOnPullListener(OnPullListener onPullListener) {

this.onPullListener = onPullListener;

}

/**

* 設置加載上一頁監聽

* @param onShowPreviousPageListener

*/

public void setOnShowPreviousPageListener(OnShowPreviousPageListener onShowPreviousPageListener) {

this.onShowPreviousPageListener = onShowPreviousPageListener;

}

/**

* 設置加載下一頁監聽

* @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時,ViewDragHelper可以用來拖拽和設置子View的位置(在ViewGroup范圍內)。

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);

//當滑到第二頁時,第二頁的top為0,第一頁為負數。

if (view1.getTop() == 0) {

view1.layout(0, 0, r, b);

view2.layout(0, 0, r, b);

viewHeight = view1.getMeasuredHeight();

view2.offsetTopAndBottom(viewHeight);// view2向下移動到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粘到頂部或底部,正在動畫中的時候,不處理Touch事件

return false;

}

boolean shouldIntercept = false;

boolean yScroll = mGestureDetector.onTouchEvent(ev);

try {

shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);

//修復導致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) {

// 兩個子View都需要跟蹤,返回true

return true;

}

@Override

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {

// 由于拖拽導致被捕獲View的位置發生改變時進行回調

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);

}

}

// 如果不重繪,拖動的時候,其他View會不顯示

ViewCompat.postInvalidateOnAnimation(GoodsDetailVerticalSlideView.this);

}

@Override

public int getViewVerticalDragRange(View child) {

// 這個用來控制拖拽過程中松手后,自動滑行的速度

return child.getHeight();

}

@Override

public void onViewReleased(View releasedChild, float xvel, float yvel) {

// 滑動松開后,需要向上或者向下粘到特定的位置, 默認是粘到最頂端

int finalTop = 0;

if (releasedChild == view1) {

// 拖動view1松手

if (yvel < -VEL_THRESHOLD || releasedChild.getTop() < -DISTANCE_THRESHOLD) {

// 向上的速度足夠大或者向上滑動的距離超過某個閾值,就滑動到view2頂端

finalTop = -viewHeight;

}

} else {

// 拖動view2松手

if (yvel > VEL_THRESHOLD || releasedChild.getTop() > DISTANCE_THRESHOLD) {

// 向下的速度足夠大或者向下滑動的距離超過某個閾值,就滑動到view1頂端

finalTop = viewHeight;

}

}

//觸發緩慢滾動

//將給定子View平滑移動到給定位置,會回調continueSettling(boolean)方法,在內部是用的ScrollerCompat來實現滑動的。

//如果返回true,表明動畫應該繼續,所以調用者應該調用continueSettling(boolean)在每個后續幀繼續動作,直到它返回false。

if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {

ViewCompat.postInvalidateOnAnimation(GoodsDetailVerticalSlideView.this);

}

}

@Override

public int clampViewPositionVertical(View child, int top, int dy) {

// 限制被拖動的子View在垂直方向的移動,可以用作邊界約束

// 阻尼滑動,讓滑動位移變為1/2,除數越大阻力越大

return child.getTop() + dy / 2;

}

}

@Override

public void computeScroll() {

// 判斷smoothSlideViewTo觸發的continueSettling(boolean)的返回值

if (mDragHelper.continueSettling(true)) {

// 如果當前被捕獲的子View還需要繼續移動,則進行重繪直到它返回false,返回false表示不用后續操作就能完成這個動作了。

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;

}

}

}

}

/** 滾動到view1頂部 */

public void smoothSlideToFirstPageTop() {

if (currentPage == 2) {

//觸發緩慢滾動

if (mDragHelper.smoothSlideViewTo(view2, 0, viewHeight)) {

ViewCompat.postInvalidateOnAnimation(this);

}

}

}

/** 滾動到view2頂部 */

public void smoothSlideToSecondPageTop() {

if (currentPage == 1) {

//觸發緩慢滾動

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) {

// 垂直滑動時dy>dx,才被認定是上下拖動

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();

}

}

其中,有一些回調監聽Listener,在具體業務邏輯處理的時候,可以跟Activity進行相應的交互,其余部分基本都有代碼注釋了。

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();

//如果滑動到了最底部,就允許繼續向上滑動加載下一頁,否者不允許

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頂部下拉時需要放大圖片,自身消費事件

allowParentTouchEvent = false;

} else {

//位于底部時上拉,讓父View消費事件

allowParentTouchEvent = isBottom();

}

} else {

//水平方向滑動,自身消費事件

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);

}

}

其中,可以根據自身業務邏輯的需要,對dispatchTouchEvent事件分發做相應的調整。

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();

//如果滑動到了最頂部,就允許繼續向下滑動加載上一頁,否者不允許

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) {

//位于頂部時下拉,讓父View消費事件

allowParentTouchEvent = isTop();

} else {

//向上滑動,自身消費事件

allowParentTouchEvent = false;

}

} else {

//水平方向滑動,自身消費事件

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);

}

}

同樣的,可以根據自身業務邏輯的需要,對dispatchTouchEvent事件分發做相應的調整。

二、如何使用

1.在Activity中使用GoodsDetailVerticalSlideView控件

(1)使用GoodsDetailVerticalSlideView控件,內部包含兩個子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)當然,也可以直接使用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

這邊有個注意點就是上拉或者下拉的時候,我們一般都會給用戶展示一個文字和圖片的指示器來提示用戶如何操作,我們只需要把指示器放在上面一部分的ScrollView布局里面即可,然后根據目前正在展示哪一部分進行顯示/隱藏以及文字圖片變化就可以了,這樣可以使我們的整個拖動效果看起來比較流暢。

(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">

......

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/371498.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/371498.shtml
英文地址,請注明出處:http://en.pswp.cn/news/371498.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

freemarker,數字,日期,布爾值常用的函數

${3.4?floor} ${3.4?ceiling} ${3.45?round} ${3.45?rtf} ${3.458?string("0.##")} ${3.42?string.percent} ${3.42?string.currency} ${date?string("yyyy-MM-dd")} ${date?date} ${date?time} ${date?datetime}${true?c} ${true?string} ${…

mysql聯合索引與Where子句優化淺析

問題描述&#xff1a;把排序、條件等一個一個去除來做測試&#xff0c;結果發現問題就出在排序部分&#xff0c;去除排序時&#xff0c;執行時間由原來的48秒變成0.3x秒。于是&#xff0c;把涉及排序的字段組成一個聯合索引alter table xx add index indexname(x1,x2,x3)&#…

有效使用Eclipse的熱門提示

以下是一些技巧&#xff0c;可以幫助您避免潛在的問題并在使用Eclipse時提高工作效率。 避免安裝問題 切勿在舊版本之上安裝新版本的Eclipse。 首先重命名舊版本&#xff0c;將其移開&#xff0c;然后將新版本解壓縮到干凈的目錄中。 恢復混亂的工作空間 對于許多開發人員來…

android拍照截圖組件,Android截圖命令screencap與視頻錄制命令screenrecord(示例代碼)...

查看幫助命令[email protected] ~$ adb shell screencap -vscreencap: invalid option -- vusage: screencap [-hp] [-d display-id] [FILENAME]-h: this message-p: save the file as a png.-d: specify the display id to capture, default 0.If FILENAME ends with .png it …

usaco 2017 February platinum

1.一條路&#xff0c;兩邊都是一個1到n的全排列&#xff0c;可以把其中一個全排列的起始位置改變&#xff08;比如123可以變成231或者312&#xff09; 然后把相同的數連起來&#xff0c;求小交叉數。 先算一下交叉數&#xff0c;然后直接一步步移動&#xff0c;O1更新一下狀態就…

Hessian 源碼簡單分析

Hessian 源碼簡單分析 Hessian 是一個rpc框架&#xff0c; 我們需要先寫一個服務端&#xff0c; 然后在客戶端遠程的調用它即可。 服務端&#xff1a; 服務端通常和spring 做集成。 首先寫一個接口&#xff1a; public interface HelloService { void sayHello(String n…

Java開發人員應該知道的三件事

對于那些長期關注JavaOne 2012會議的讀者來說&#xff0c;這是一篇有趣的文章。 我最近對Java冠軍Heinz Kabutz的采訪引起了我的注意&#xff1b; 包括他的Java內存難題程序&#xff0c;從Java內存管理的角度來看&#xff0c;這很有啟發性。 采訪中有一個特別的部分吸引了我的注…

android怎么垂直居中且靠右,placeholder 靠右垂直居中/位置兼容

1.input輸入框文字靠右垂直居中。2.placehoder提示同樣靠右垂直居中。( placeholder是HTML5 input的新屬性&#xff0c;英文意思是占位符&#xff0c;它一般表示input輸入框的默認提示值。)css代碼input {text-align: right;font-size:0.3rem;width:100%;height:0.78rem;line-…

Python-Matplotlib 18 注釋

Python-Matplotlib 18 注釋 EG1: import numpy as np import matplotlib.pyplot as plty np.arange(-5, 6,1) plt.plot(y, y*y) plt.annotate(Annotate , xy(0,1) , xytext(0,5) ,arrowpropsdict(facecolorr , frac0.2 ))plt.show()轉載于:https://www.cnblogs.com/zsr0401/p/…

while和for循環

循環結構圖&#xff1a; 循環結構主要分為兩種&#xff1a;有while和for兩種循環&#xff0c;while又分為do{...}while和while{...},do...while表示先執行后判斷&#xff0c;而while循壞表示先判斷后執行&#xff0c;如果循環條件都不滿足的情況下&#xff0c;do...while至少執…

通過beforeClass和afterClass設置增強Spring Test Framework

如何允許實例方法作為JUnit BeforeClass行為運行 JUnit允許您在所有測試方法調用之前和之后一次在類級別上設置方法。 但是&#xff0c;通過有意設計&#xff0c;他們將其限制為僅使用BeforeClass和AfterClass批注的靜態方法。 例如&#xff0c;此簡單的演示顯示了典型的Junit設…

華為鴻蒙出來正當時,關于華為鴻蒙操作系統,中興率先表態

原標題&#xff1a;關于華為鴻蒙操作系統&#xff0c;中興率先表態 來源&#xff1a;科技數碼迷進入2021年之后中興這個品牌的存在感越來越強了&#xff0c;并且還學會了借勢營銷。每當國內智能手機領域有大事之時總會看到中興或紅魔手機的身影。這說明在5G過渡期中興要借個機會…

條件變量(Condition Variable)詳解

轉載于&#xff1a;http://blog.csdn.net/erickhuang1989/article/details/8754357 條件變量(Condtion Variable)是在多線程程序中用來實現“等待->喚醒”邏輯常用的方法。舉個簡單的例子&#xff0c;應用程序A中包含兩個線程t1和t2。t1需要在bool變量test_cond為true時才能…

C++中的深拷貝和淺拷貝 QT中的深拷貝,淺拷貝和隱式共享

下面是C中定義的深&#xff0c;淺拷貝 當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候&#xff0c;拷貝構造函數就會被自動調用。也就是說&#xff0c;當類的對象需要拷貝時&#xff0c;拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數&#…

使用PowerMock模擬構造函數

我認為&#xff0c;依賴項注入的主要好處之一是可以將模擬和/或存根對象注入代碼中&#xff0c;以提高可測試性&#xff0c;增加測試覆蓋率并編寫更好&#xff0c;更有意義的測試。 但是&#xff0c;有時候您會遇到一些不使用依賴注入的傳統代碼&#xff0c;而是通過組合而不是…

Brackets (區間DP)

個人心得&#xff1a;今天就做了這些區間DP&#xff0c;這一題開始想用最長子序列那些套路的&#xff0c;后面發現不滿足無后效性的問題&#xff0c;即&#xff08;&#xff0c;&#xff09;的配對 對結果有一定的影響&#xff0c;后面想著就用上一題的思想就慢慢的從小一步一步…

android生成aar無效,android studio生成aar包并在其他工程引用aar包的方法

1.aar包是android studio下打包android工程中src、res、lib后生成的aar文件&#xff0c;aar包導入其他android studio 工程后&#xff0c;其他工程可以方便引用源碼和資源文件2.生成aar包步驟&#xff1a;①.用android studio打開一個工程&#xff0c;然后新建一個Module&#…

《劍指offer》— JavaScript(3)從尾到頭打印鏈表

從尾到頭打印鏈表 題目描述 輸入一個鏈表&#xff0c;從尾到頭打印鏈表每個節點的值。 實現代碼 /*function ListNode(x){this.val x;this.next null; }*/ function printListFromTailToHead(head) {var res[];while(head){res.unshift(head.val);headhead.next;}return res;…

JUnit測試Spring Service和DAO(帶有內存數據庫)

這篇文章描述了如何為Spring Web Application的Services和DAO實現JUnit測試。 它建立在Spring MVC-Service-DAO-Persistence Architecture Example的基礎上 。 從Github的Spring-Web-JPA-Testing目錄中可以找到該示例。 提醒 測試裝置 –固定狀態&#xff0c;用作運行測試的基…

c# 正則獲取html標簽內容,c# – 使用正則表達式在多個HTML標記之間獲取文本

使用正則表達式,我希望能夠在多個DIV標記之間獲取文本.例如,以下內容&#xff1a;first html taganother tag輸出&#xff1a;first html taganother tag我使用的正則表達式模式只匹配我的最后一個div標簽并錯過了第一個.碼&#xff1a;static void Main(string[] args){string…