安卓自動滾動自定義控制元件
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/48719871
2016大家新年好!這是今年的第一篇文章,那麼應CSDN工作人員的建議,為了能給大家帶來更好的閱讀體驗,我也是將部落格換成了寬屏版。另外,作為一個對新鮮事物從來後知後覺的人,我終於也在新的一年裡改用MarkDown編輯器來寫部落格了,希望大家在我的部落格裡也能體驗到新年新的氣象。
我寫部落格的題材很多時候取決於平時大家問的問題,最近一段時間有不少朋友都問到ViewPager是怎麼實現的。那ViewPager相信每個人都再熟悉不過了,因此它實在是太常用了,我們可以藉助ViewPager來輕鬆完成頁面之間的滑動切換效果,但是如果問到它是如何實現的話,我感覺大部分人還是比較陌生的, 為此我也是做了一番功課。其實說到ViewPager最基本的實現原理主要就是兩部分內容,一個是事件分發,一個是Scroller,那麼對於事件分發,其實我在很早之前就已經寫過了相關的內容,感興趣的朋友可以去閱讀
Scroller是一個專門用於處理滾動效果的工具類,可能在大多數情況下,我們直接使用Scroller的場景並不多,但是很多大家所熟知的控制元件在內部都是使用Scroller來實現的,如ViewPager、ListView等。而如果能夠把Scroller的用法熟練掌握的話,我們自己也可以輕鬆實現出類似於ViewPager這樣的功能。那麼首先新建一個ScrollerTest專案,今天就讓我們通過例子來學習一下吧。
先撇開Scroller類不談,其實任何一個控制元件都是可以滾動的,因為在View類當中有scrollTo()和scrollBy()這兩個方法,如下圖所示:
這兩個方法都是用於對View進行滾動的,那麼它們之間有什麼區別呢?簡單點講,scrollBy()方法是讓View相對於當前的位置滾動某段距離,而scrollTo()方法則是讓View相對於初始的位置滾動某段距離。這樣講大家理解起來可能有點費勁,我們來通過例子實驗一下就知道了。
修改activity_main.xml中的佈局檔案,程式碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools ="http://schemas.android.com/tools"android:id="@+id/layout"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.example.guolin.scrollertest.MainActivity">
<Button
android:id="@+id/scroll_to_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="scrollTo"/>
<Button
android:id="@+id/scroll_by_btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="scrollBy"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
外層我們使用了一個LinearLayout,然後在裡面包含了兩個按鈕,一個用於觸發scrollTo邏輯,一個用於觸發scrollBy邏輯。
接著修改MainActivity中的程式碼,如下所示:
public classMainActivityextendsAppCompatActivity {
private LinearLayout layout;
private Button scrollToBtn;
private Button scrollByBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (LinearLayout) findViewById(R.id.layout);
scrollToBtn = (Button) findViewById(R.id.scroll_to_btn);
scrollByBtn = (Button) findViewById(R.id.scroll_by_btn);
scrollToBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
layout.scrollTo(-60, -100);
}
});
scrollByBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
layout.scrollBy(-60, -100);
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
沒錯,程式碼就是這麼簡單。當點選了scrollTo按鈕時,我們呼叫了LinearLayout的scrollTo()方法,當點選了scrollBy按鈕時,呼叫了LinearLayout的scrollBy()方法。那有的朋友可能會問了,為什麼都是呼叫的LinearLayout中的scroll方法?這裡一定要注意,不管是scrollTo()還是scrollBy()方法,滾動的都是該View內部的內容,而LinearLayout中的內容就是我們的兩個Button,如果你直接呼叫button的scroll方法的話,那結果一定不是你想看到的。
另外還有一點需要注意,就是兩個scroll方法中傳入的引數,第一個引數x表示相對於當前位置橫向移動的距離,正值向左移動,負值向右移動,單位是畫素。第二個引數y表示相對於當前位置縱向移動的距離,正值向上移動,負值向下移動,單位是畫素。
那說了這麼多,scrollTo()和scrollBy()這兩個方法到底有什麼區別呢?其實執行一下程式碼我們就能立刻知道了:
可以看到,當我們點選scrollTo按鈕時,兩個按鈕會一起向右下方滾動,因為我們傳入的引數是-60和-100,因此向右下方移動是正確的。但是你會發現,之後再點選scrollTo按鈕就沒有任何作用了,介面不會再繼續滾動,只有點選scrollBy按鈕介面才會繼續滾動,並且不停點選scrollBy按鈕介面會一起滾動下去。
現在我們再來回頭看一下這兩個方法的區別,scrollTo()方法是讓View相對於初始的位置滾動某段距離,由於View的初始位置是不變的,因此不管我們點選多少次scrollTo按鈕滾動到的都將是同一個位置。而scrollBy()方法則是讓View相對於當前的位置滾動某段距離,那每當我們點選一次scrollBy按鈕,View的當前位置都進行了變動,因此不停點選會一直向右下方移動。
通過這個例子來理解,相信大家已經把scrollTo()和scrollBy()這兩個方法的區別搞清楚了,但是現在還有一個問題,從上圖中大家也能看得出來,目前使用這兩個方法完成的滾動效果是跳躍式的,沒有任何平滑滾動的效果。沒錯,只靠scrollTo()和scrollBy()這兩個方法是很難完成ViewPager這樣的效果的,因此我們還需要藉助另外一個關鍵性的工具,也就我們今天的主角Scroller。
Scroller的基本用法其實還是比較簡單的,主要可以分為以下幾個步驟:
1. 建立Scroller的例項
2. 呼叫startScroll()方法來初始化滾動資料並重新整理介面
3. 重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
那麼下面我們就按照上述的步驟,通過一個模仿ViewPager的簡易例子來學習和理解一下Scroller的用法。
新建一個ScrollerLayout並讓它繼承自ViewGroup來作為我們的簡易ViewPager佈局,程式碼如下所示:
/**
* Created by guolin on 16/1/12.
*/
public classScrollerLayoutextendsViewGroup {
/**
* 用於完成滾動操作的例項
*/
private Scroller mScroller;
/**
* 判定為拖動的最小移動畫素數
*/
private int mTouchSlop;
/**
* 手機按下時的螢幕座標
*/
private float mXDown;
/**
* 手機當時所處的螢幕座標
*/
private float mXMove;
/**
* 上次觸發ACTION_MOVE事件時的螢幕座標
*/
private float mXLastMove;
/**
* 介面可滾動的左邊界
*/
private int leftBorder;
/**
* 介面可滾動的右邊界
*/
private int rightBorder;
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 第一步,建立Scroller的例項
mScroller = new Scroller(context);
ViewConfiguration configuration = ViewConfiguration.get(context);
// 獲取TouchSlop值
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 為ScrollerLayout中的每一個子控制元件測量大小
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 為ScrollerLayout中的每一個子控制元件在水平方向上進行佈局
childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
// 初始化左右邊界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDown = ev.getRawX();
mXLastMove = mXDown;
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float diff = Math.abs(mXMove - mXDown);
mXLastMove = mXMove;
// 當手指拖動值大於TouchSlop值時,認為應該進行滾動,攔截子控制元件的事件
if (diff > mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mXMove = event.getRawX();
int scrolledX = (int) (mXLastMove - mXMove);
if (getScrollX() + scrolledX < leftBorder) {
scrollTo(leftBorder, 0);
return true;
} else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
scrollTo(rightBorder - getWidth(), 0);
return true;
}
scrollBy(scrolledX, 0);
mXLastMove = mXMove;
break;
case MotionEvent.ACTION_UP:
// 當手指擡起時,根據當前的滾動值來判定應該滾動到哪個子控制元件的介面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
// 第二步,呼叫startScroll()方法來初始化滾動資料並重新整理介面
mScroller.startScroll(getScrollX(), 0, dx, 0);
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
// 第三步,重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
整個Scroller用法的程式碼都在這裡了,程式碼並不長,一共才100多行,我們一點點來看。
首先在ScrollerLayout的建構函式裡面我們進行了上述步驟中的第一步操作,即建立Scroller的例項,由於Scroller的例項只需建立一次,因此我們把它放到建構函式裡面執行。另外在構建函式中我們還初始化的TouchSlop的值,這個值在後面將用於判斷當前使用者的操作是否是拖動。
接著重寫onMeasure()方法和onLayout()方法,在onMeasure()方法中測量ScrollerLayout裡的每一個子控制元件的大小,在onLayout()方法中為ScrollerLayout裡的每一個子控制元件在水平方向上進行佈局。如果有朋友對這兩個方法的作用還不理解,可以參照我之前寫的一篇文章 Android檢視繪製流程完全解析,帶你一步步深入瞭解View(二) 。
接著重寫onInterceptTouchEvent()方法, 在這個方法中我們記錄了使用者手指按下時的X座標位置,以及使用者手指在螢幕上拖動時的X座標位置,當兩者之間的距離大於TouchSlop值時,就認為使用者正在拖動佈局,然後我們就將事件在這裡攔截掉,阻止事件傳遞到子控制元件當中。
那麼當我們把事件攔截掉之後,就會將事件交給ScrollerLayout的onTouchEvent()方法來處理。如果當前事件是ACTION_MOVE,說明使用者正在拖動佈局,那麼我們就應該對佈局內容進行滾動從而影響拖動事件,實現的方式就是使用我們剛剛所學的scrollBy()方法,使用者拖動了多少這裡就scrollBy多少。另外為了防止使用者拖出邊界這裡還專門做了邊界保護,當拖出邊界時就呼叫scrollTo()方法來回到邊界位置。
如果當前事件是ACTION_UP時,說明使用者手指擡起來了,但是目前很有可能使用者只是將佈局拖動到了中間,我們不可能讓佈局就這麼停留在中間的位置,因此接下來就需要藉助Scroller來完成後續的滾動操作。首先這裡我們先根據當前的滾動位置來計算佈局應該繼續滾動到哪一個子控制元件的頁面,然後計算出距離該頁面還需滾動多少距離。接下來我們就該進行上述步驟中的第二步操作,呼叫startScroll()方法來初始化滾動資料並重新整理介面。startScroll()方法接收四個引數,第一個引數是滾動開始時X的座標,第二個引數是滾動開始時Y的座標,第三個引數是橫向滾動的距離,正值表示向左滾動,第四個引數是縱向滾動的距離,正值表示向上滾動。緊接著呼叫invalidate()方法來重新整理介面。
現在前兩步都已經完成了,最後我們還需要進行第三步操作,即重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯 。在整個後續的平滑滾動過程中,computeScroll()方法是會一直被呼叫的,因此我們需要不斷呼叫Scroller的computeScrollOffset()方法來進行判斷滾動操作是否已經完成了,如果還沒完成的話,那就繼續呼叫scrollTo()方法,並把Scroller的curX和curY座標傳入,然後重新整理介面從而完成平滑滾動的操作。
現在ScrollerLayout已經準備好了,接下來我們修改activity_main.xml佈局中的內容,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<com.example.guolin.scrollertest.ScrollerLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"
>
<Button
android:layout_width="match_parent"android:layout_height="100dp"android:text="This is first child view"/>
<Button
android:layout_width="match_parent"android:layout_height="100dp"android:text="This is second child view"/>
<Button
android:layout_width="match_parent"android:layout_height="100dp"android:text="This is third child view"/>
</com.example.guolin.scrollertest.ScrollerLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
可以看到,這裡我們在ScrollerLayout中放置了三個按鈕用來進行測試,其實這裡不僅可以放置按鈕,放置任何控制元件都是沒問題的。
最後MainActivity當中刪除掉之前測試的程式碼:
public classMainActivityextendsAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
好的,所有程式碼都在這裡了,現在我們可以執行一下程式來看一看效果了,如下圖所示:
怎麼樣,是不是感覺有點像一個簡易的ViewPager了?其實藉助Scroller,很多漂亮的滾動效果都可以輕鬆完成,比如實現圖片輪播之類的特效。當然就目前這一個例子來講,我們只是藉助它來學習了一下Scroller的基本用法,例子本身有很多的功能點都沒有去實現,比如說ViewPager會根據使用者手指滑動速度的快慢來決定是否要翻頁,這個功能在我們的例子中並沒有體現出來,不過大家也可以當成自我訓練來嘗試實現一下。
好的,那麼本篇文章就到這裡,相信通過這篇文章的學習,大家已經能夠熟練掌握Scroller的使用方法了,當然ViewPager的內部實現要比這複雜得多,如果有朋友對ViewPager的原始碼感興趣也可以嘗試去讀一下,不過一定需要非常紮實的基本功才行。
相關推薦
安卓自動滾動自定義控制元件
轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/48719871 2016大家新年好!這是今年的第一篇文章,那麼應CSDN工作人員的建議,為了能給大家帶來更好的閱讀體驗,我也是將部落格換成了寬屏版。另外,作為一個對新鮮事物從來後知後覺的人,我
安卓自定義控制元件原理
Android自定義控制元件之基本原理 前言: 在日常的Android開發中會經常和控制元件打交道,有時Android提供的控制元件未必能滿足業務的需求,這個時候就需要我們實現自定義一些控制元件,今天先大致瞭解一下自定義控制元件的要求和實現的基本原理。
學習安卓小碼哥自定義控制元件的筆記(三)
package com.example.wtz.viewpagerdemo; import android.graphics.Color; import android.support.annotation.NonNull; import android.support.v4.view
學習安卓小碼哥自定義控制元件的筆記(六)
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ScrollingView"> <attr name="scrolli
安卓專案實戰之仿微信朋友圈的九宮格自定義控制元件
效果圖 圖片展示形式 1、當只有1張圖時,可以自己定製圖片寬高,也可以使用預設九宮格的寬高; 2、當只有4張圖時,以2*2的方式顯示; 3、除以上兩種情況下,都是按照3列方式顯示,但這時有一些細節: a、如果只有9張圖,當然是以3*3的方式顯示;
安卓仿iphone日期選項器 仿iphone日期選擇自定義控制元件
原來的DatePicker總有這樣那樣的不如意,看到ipad的日期選擇,感覺很好,如果google android date like iphone, 得到Android Wheel,專案地址:https://code.google.com/p/android-wheel/
安卓禁止ScrollView內的控制元件改變之後自動滾動
在不希望控制元件改變時滾動條滾動。那麼設定次控制元件或者佈局的屬性 android:focusable="true" android:focusableInTouchMode="tru
【安卓自定義控制元件】自定義ViewGroup實現透明背景的ViewPager效果
HelloWorld! 作為一名屌絲程式設計師,在部落格園寫第一篇技術部落格內心是無比激動滴,其實作為一名忙成狗的Android開發人員,一直覺得自己永遠都不會有時間去寫部落格, 因為我TM連找女朋友的時間都沒用== 言歸正傳,今天自定義控制元件系列要實現的效果是自定義Vi
【安卓筆記】自定義view之組合控制元件
組合控制元件即將若干個系統已有的控制元件組合到一塊形成一個組合控制元件,比如帶返回按鈕的標題欄就是一個最簡單的組合控制元件。 使用組合控制元件的好處是提高程式碼的複用性,一處定義多處使用。 下面我們
安卓自定義控制元件-實現IOS版UC瀏覽器三點載入動畫效果
1.實現分析 廢話不多說,看下IOS版UC瀏覽器的載入效果 簡單畫個圖看下整個過程 1.B圓的圓心移動的座標為:A圓和B圓的圓心的距離L的中點為圓心O1的下半圓的運動軌跡經過的座標,就有一個由B位置到A位置圓周運動的軌跡。 2.C圓的圓心
安卓自定義控制元件全解
全棧工程師開發手冊 (作者:欒鵬) 安卓自定義控制元件:包含LinearLayout、RelativeLayout、GridView、Button等 本文以LinearLayout為例 首先要有一個自定義xml佈局檔案 我們這裡儲
Android自定義控制元件實戰——滾動選擇器PickerView
手機裡設定鬧鐘需要選擇時間,那個選擇時間的控制元件就是滾動選擇器,前幾天用手機刷了MIUI,發現自帶的那個時間選擇器效果挺好看的,於是就自己仿寫了一個,權當練手。先來看效果:
android自定義控制元件自動換行效果實現
第一篇部落格裡面有介紹一篇關於自動換行實現諸多自定義控制元件跟各種效果的博文,但是礙於當初技術能力有限,寫的jar包裡的程式碼亂七八糟,在最近忙完了手頭的工作,不經意間翻看了之前的程式碼,真是慘不忍睹,隨決定重新封裝。重新編寫的android-custom-vg前
Qt QScrollArea顯示滾動條(新增自定義控制元件)
最近在做專案,想要使用一個帶滾動條的窗體來顯示一些資訊,可以自己重寫一個區域再關聯一個QScrollBar,但是這樣一來,工作量貌似挺大,之前就知道有QScrollArea物件,但是一直沒用過,心裡想著應該可以在上面布一些控制元件物件,但是後來查了幫助文件,怎麼也沒發現類似
Android 自定義控制元件-自動換行的流線性佈局-DragFlowLayout
效果圖 步驟 1.繼承RelativeLayout 2.複寫onMeasure 3.複寫onLayout 4.提供介面回撥 5.使用ViewDragHelper實現對子控制元件拖拽 繼承RelativeLayout public
[WPF自定義控制元件庫] 讓Form在載入後自動獲得焦點
1. 需求 載入後讓第一個輸入框或者焦點是個很基本的功能,典型的如“登入”對話方塊。一般來說“登入”對話方塊載入後“使用者名稱”應該馬上獲得焦點,使用者只需輸入使用者名稱,點選Tab,再輸入密碼,點選回車就完成了登入操作。 在WPF中要讓一個控制元件在載入時獲得焦點應該很簡單,只需要在Loaded事件後呼叫F
[WPF自定義控制元件庫] 關於ScrollViewr和滾動輪劫持(scroll-wheel-hijack)
1. 什麼是滾動輪劫持 這篇文章介紹一個很簡單的繼承自ScrollViewer的控制元件: public class ExtendedScrollViewer : ScrollViewer { protected override void OnMouseWheel(MouseWheelEventAr
(五十三)c#Winform自定義控制元件-滾動文字
前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr
(六十九)c#Winform自定義控制元件-垂直滾動條
前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_control.
(八十九)c#Winform自定義控制元件-自定義滾動條(treeview、panel、datagridview、listbox、listview、textbox)
官網 http://www.hzhcontrols.com/ 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwva