Android自定義LinearLayout實(shí)現(xiàn)淘寶詳情頁(yè)
1.簡(jiǎn)單說(shuō)明
淘寶詳情頁(yè)就不用我一一介紹了,昨天逛淘寶看到這個(gè)效果時(shí),讓我想起了去年剛學(xué)習(xí)Android只會(huì)使用現(xiàn)成的時(shí)候,當(dāng)時(shí)在網(wǎng)上找了一個(gè)這種效果的使用了,并不懂怎么實(shí)現(xiàn)的。現(xiàn)在就看到一種效果就想自己實(shí)現(xiàn)一下,我想這就是剛接觸某個(gè)知識(shí)時(shí)的好奇心吧
說(shuō)走咱就走啊,本文只是介紹一種實(shí)現(xiàn)思路,網(wǎng)上也已經(jīng)有了很多種實(shí)現(xiàn)方式,有問(wèn)題請(qǐng)指正
效果圖(我有很用心的找美女圖的)

2.實(shí)現(xiàn)思路
繼承LinearLayout,設(shè)置方向?yàn)榇怪?br />
控件中有兩個(gè)ScrollView,至于為什么要使用ScrollView,主要是因?yàn)閮?nèi)容超過(guò)一頁(yè)時(shí)省去自己處理滑動(dòng)
關(guān)鍵是事件分發(fā)處理。監(jiān)聽(tīng)兩個(gè)ScrollView的滑動(dòng)事件,當(dāng)?shù)谝豁?yè)滑動(dòng)到底部時(shí),再向上拖動(dòng)時(shí),攔截事件,判斷距離,超過(guò)設(shè)定值時(shí),滑動(dòng)到第二頁(yè),否則回彈;同理,當(dāng)?shù)诙?yè)滑動(dòng)到頂部時(shí),再向下拖動(dòng)時(shí),攔截事件,判斷距離,超過(guò)設(shè)定值時(shí),滑動(dòng)到第一頁(yè),否則回彈(還有很多細(xì)節(jié)需要結(jié)合代碼講解)
關(guān)于回彈和滑動(dòng)換頁(yè)使用的是Scroller,對(duì)于Scroller的使用,本文不做過(guò)多解釋
3.實(shí)現(xiàn)
3.1重寫ScrollView
根據(jù)實(shí)現(xiàn)思路,我們需要監(jiān)聽(tīng)ScrollView是否滑動(dòng)到頂部和底部,但是ScrollView的setOnScrollChangeListener()方法在api23才添加。主要是重寫ScrollViewonScrollChanged(int l, int t, int oldl, int oldt)方法。
l:當(dāng)前水平方向滾動(dòng)值,和getScrollX()相等
t:當(dāng)前豎直方向滾動(dòng)值,和getScrollY()相等
oldl:上一次水平滾動(dòng)值
oldt:上一次豎直滾動(dòng)值
監(jiān)聽(tīng)接口:
public interface OnScrollEndListener {
void scrollToBottom(View view);
void scrollToTop(View view);
void scrollToMiddle(View view);
}
onScrollChanged方法
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(t == 0){
if (mOnScrollBottomListener != null) {
mOnScrollBottomListener.scrollToTop(this);
}
} else if(t + getMeasuredHeight() >= getChildAt(0).getMeasuredHeight()){
if (mOnScrollBottomListener != null) {
mOnScrollBottomListener.scrollToBottom(this);
}
} else {
if (mOnScrollBottomListener != null) {
mOnScrollBottomListener.scrollToMiddle(this);
}
}
}
3.2重寫onMeasure方法、page的獲取與設(shè)置
顯示調(diào)用第二個(gè)自孩子的測(cè)量方法,不然尺寸有可能為0
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 顯示調(diào)用第二個(gè)自孩子的測(cè)量方法,不然尺寸有可能為0
*/
View child2 = getChildAt(1);
if (child2 != null) {
child2.measure(widthMeasureSpec, heightMeasureSpec);
}
}
在onFinishInflate中初始化兩個(gè)頁(yè)面
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(getChildCount() == 2){
View child1 = getChildAt(0);
if (child1 instanceof ScrollEndScrollView){
scrollView1 = (ScrollEndScrollView) child1;
}
View child2 = getChildAt(1);
if(child2 instanceof ScrollEndScrollView){
scrollView2 = (ScrollEndScrollView) child2;
}
}
initEvent();
}
為兩個(gè)頁(yè)面設(shè)置滑動(dòng)監(jiān)聽(tīng)
private ScrollEndScrollView.OnScrollEndListener scrollEndListener = new ScrollEndScrollView.OnScrollEndListener() {
@Override
public void scrollToBottom(View view) {
if(view == scrollView1){
isToBotttom = true;
}
}
@Override
public void scrollToTop(View view) {
if(view == scrollView2){
isToTop = true;
}
}
@Override
public void scrollToMiddle(View view) {
if(view == scrollView1){
isToBotttom = false;
}
if(view == scrollView2){
isToTop = false;
}
}
};
3.3Scroller使用的幾步
Scroller的英文解釋是:
This class encapsulates scrolling. You can use scrollers (Scroller or OverScroller) to collect the data you need to produce a scrolling animation—for example, in response to a fling gesture. Scrollers track scroll offsets for you over time, but they don't automatically apply those positions to your view. It's your responsibility to get and apply new coordinates at a rate that will make the scrolling animation look smooth.
此類封裝滾動(dòng)。您可以使用滾動(dòng)條(滾輪或OverScroller)收集你需要制作一個(gè)滾動(dòng)的動(dòng)畫,例如,響應(yīng)一扔手勢(shì)的數(shù)據(jù)。滾動(dòng)條為您跟蹤滾動(dòng)偏移量隨著時(shí)間的推移,但他們不會(huì)自動(dòng)將新的位置設(shè)置到View中。你的任務(wù)是獲取并使用一個(gè)合適的速度,使?jié)L動(dòng)動(dòng)畫看起來(lái)更平滑。
簡(jiǎn)而言之,有關(guān)滑動(dòng)的你都可以使用這個(gè)實(shí)現(xiàn)。
需要重寫的方法
@Override
public void computeScroll() {
super.computeScroll();
//先判斷mScroller滾動(dòng)是否完成
if (mScroller.computeScrollOffset()) {
//這里調(diào)用View的scrollTo()完成實(shí)際的滾動(dòng)
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必須調(diào)用該方法,否則不一定能看到滾動(dòng)效果
postInvalidate();
}
}
輔助方法
//調(diào)用此方法設(shè)置滾動(dòng)的相對(duì)偏移
public void smoothScrollBy(int dx, int dy) {
//設(shè)置mScroller的滾動(dòng)偏移量
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, Math.max(300, Math.abs(dy)));
invalidate();//這里必須調(diào)用invalidate()才能保證computeScroll()會(huì)被調(diào)用,否則不一定會(huì)刷新界面,看不到滾動(dòng)效果
}
//調(diào)用此方法滾動(dòng)到目標(biāo)位置
public void smoothScrollTo(int fx, int fy) {
int dx = fx - mScroller.getFinalX();
int dy = fy - mScroller.getFinalY();
smoothScrollBy(dx, dy);
}
3.4事件分發(fā)
最關(guān)鍵的部分,邏輯稍復(fù)雜,細(xì)節(jié)處理較多。這里重寫dispatchTouchEvent。
顯示第一頁(yè)時(shí)
未滑動(dòng)到底部,事件由scrollView1自己處理
滑動(dòng)到底部時(shí),如果繼續(xù)向上拖動(dòng),攔截事件,父控件處理滑動(dòng);繼續(xù)向下拖動(dòng)時(shí),如果父控件(即該控件)當(dāng)前滾動(dòng)最后位置(mScroller.getFinalY())不為0, 如果父控件繼續(xù)滾動(dòng)不會(huì)出現(xiàn)負(fù)值時(shí)(出現(xiàn)負(fù)值時(shí)會(huì)導(dǎo)致頭部空白,因?yàn)檫@時(shí)是父控件控制,scrollView1不可滑動(dòng)),不攔截事件,父控件處理滑動(dòng),否則,強(qiáng)制滑動(dòng)到0位置,并把事件下發(fā)給子控件
顯示第二頁(yè)時(shí)
未滑動(dòng)到最頂部時(shí),事件由scrollView2自己處理
滑動(dòng)到頂部時(shí),如果繼續(xù)向下拖動(dòng),攔截事件,父控件處理滑動(dòng);繼續(xù)向上拖動(dòng)時(shí),如果父控件當(dāng)前滾動(dòng)位置小于第一頁(yè)高度,攔截事件,父控件處理滑動(dòng),否則,滑動(dòng)到第二頁(yè)起始位置,并把事件下發(fā)給子控件
ACTION_MOVE中進(jìn)行事件分發(fā),ACTION_UP中進(jìn)行切換頁(yè)面、回彈
關(guān)于使用scroller滑動(dòng),實(shí)現(xiàn)彈性效果,簡(jiǎn)單實(shí)現(xiàn)請(qǐng)看這里簡(jiǎn)單的彈性實(shí)現(xiàn)代碼
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int yPosition = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mScroller.abortAnimation();
mLastY = yPosition;
mMoveY = 0;
break;
case MotionEvent.ACTION_MOVE:
mMoveY = (mLastY - yPosition);
mLastY = yPosition;
if(isToBotttom){
if(mMoveY > 0){
//向上
smoothScrollBy(0, mMoveY);
return true;
} else {
//向下
if(mScroller.getFinalY() != 0){
//這是出于第一頁(yè)和第二頁(yè)顯示連接處
if(getScrollY() + mMoveY > 0){
smoothScrollBy(0, mMoveY);
return true;
} else{
smoothScrollTo(0, 0);
return super.dispatchTouchEvent(ev);
}
}
}
}
else if(isToTop){
if(mMoveY < 0){
//向下
smoothScrollBy(0, mMoveY);
return true;
} else {
//向上
if(mScroller.getFinalY() < scrollView1.getHeight()){
//這是出于第一頁(yè)和第二頁(yè)顯示連接處
smoothScrollBy(0, mMoveY);
return true;
} else {
smoothScrollTo(0, scrollView1.getHeight());
return super.dispatchTouchEvent(ev);
}
}
}
//處理快速滑動(dòng)時(shí)兩頁(yè)覆蓋問(wèn)題
if(pageIndex == 0){
smoothScrollTo(0, 0);
} else if(pageIndex == 1){
smoothScrollTo(0, scrollView1.getHeight());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(isToBotttom){
if(Math.abs(getScrollY()) > TO_NEXT_PAGE_HEIGHT){
//移動(dòng)到第二頁(yè)
pageIndex = 1;
smoothScrollTo(0, scrollView1.getHeight());
isToBotttom = false;
isToTop = true;
} else {
//回彈
smoothScrollBy(0, -mScroller.getFinalY());
}
} else if(isToTop){
if(scrollView1.getHeight() - getScrollY() > TO_NEXT_PAGE_HEIGHT){
//移動(dòng)到第一頁(yè)
pageIndex = 0;
smoothScrollTo(0, 0);
isToBotttom = true;
isToTop = false;
} else {
//回彈
smoothScrollTo(0, scrollView1.getHeight());
}
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
4.總結(jié)
實(shí)現(xiàn)該控件,需要掌握的知識(shí)點(diǎn)主要是自定義控件的基本步驟、Scroller的基本使用和事件分發(fā),當(dāng)然這里最關(guān)鍵的處理還是事件分發(fā)。開(kāi)頭也說(shuō)了,雖然這個(gè)有很多人實(shí)現(xiàn)過(guò)了,但還是想用自己的方式實(shí)現(xiàn)一遍。大笑三聲,哈哈哈,又實(shí)現(xiàn)一個(gè)自定義控件…博主還在自定義控件學(xué)習(xí)階段,請(qǐng)謹(jǐn)慎使用該控件到項(xiàng)目中。
5.下載
https://github.com/LineChen/TwoPageLayout
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
深入Android中BroadcastReceiver的兩種注冊(cè)方式(靜態(tài)和動(dòng)態(tài))詳解
這篇文章主要介紹了深入Android中BroadcastReceiver的兩種注冊(cè)方式(靜態(tài)和動(dòng)態(tài))詳解,具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12
Android從0到完整項(xiàng)目(1)使用Android studio 創(chuàng)建項(xiàng)目詳解
本篇文章主要介紹了Android從0到完整項(xiàng)目(1)使用Android studio 創(chuàng)建項(xiàng)目詳解,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
Flutter 移動(dòng)程序安全性提高的八個(gè)建議
這篇文章主要為大家介紹了Flutter 移動(dòng)程序安全性提高建議詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Android利用ShaderMask實(shí)現(xiàn)花里胡哨的文字特效
我們的?App?大部分時(shí)候的文字都是一種顏色,實(shí)際上,文字的顏色也可以多姿多彩。我們今天就來(lái)介紹一個(gè)能夠輕松實(shí)現(xiàn)文字漸變色的組件?——?ShaderMask,感興趣的可以了解一下2022-12-12
Ubuntu中為Android系統(tǒng)上實(shí)現(xiàn)內(nèi)置C可執(zhí)行程序測(cè)試Linux內(nèi)核驅(qū)動(dòng)程序
本文主要介紹在Ubuntu上為Android系統(tǒng)內(nèi)置C可執(zhí)行程序測(cè)試Linux內(nèi)核驅(qū)動(dòng)程序,這里對(duì)測(cè)試Linux 內(nèi)核驅(qū)動(dòng)程序做了詳細(xì)介紹,并附有代碼示例,有興趣的小伙伴可以參考下2016-08-08
一個(gè)吸頂Item的簡(jiǎn)單實(shí)現(xiàn)方法分享
這篇文章主要給大家介紹了一個(gè)吸頂Item的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09

