Android實(shí)現(xiàn)圖片滾動(dòng)和頁(yè)簽控件功能的實(shí)現(xiàn)代碼
首先題外話,今天早上起床的時(shí)候,手滑一下把我的手機(jī)甩了出去,結(jié)果陪伴我兩年半的摩托羅拉里程碑一代就這么安息了,于是我今天決定怒更一記,紀(jì)念我死去的愛機(jī)。
如果你是網(wǎng)購(gòu)達(dá)人,你的手機(jī)上一定少不了淘寶客戶端。關(guān)注特效的人一定都會(huì)發(fā)現(xiàn),淘寶不管是網(wǎng)站還是手機(jī)客戶端,主頁(yè)上都會(huì)有一個(gè)圖片滾動(dòng)播放器,上面展示一些它推薦的商品。這個(gè)幾乎可以用淘寶來(lái)冠名的功能,看起來(lái)還是挺炫的,我們今天就來(lái)實(shí)現(xiàn)一下。
實(shí)現(xiàn)原理其實(shí)還是之前那篇文章Android仿人人客戶端滑動(dòng)菜單的側(cè)滑菜單效果,史上最簡(jiǎn)單的側(cè)滑實(shí)現(xiàn) ,算是以那個(gè)原理為基礎(chǔ)的另外一個(gè)變種。正所謂一通百通,真正掌握一種方法之后,就可以使用這個(gè)方法變換出各種不通的效果。
今天仍然還是實(shí)現(xiàn)一個(gè)自定義控件,然后我們?cè)谌我釧ctivity的布局文件中引用一下,即可實(shí)現(xiàn)圖片滾動(dòng)器的效果。
在Eclipse中新建一個(gè)Android項(xiàng)目,項(xiàng)目名就叫做SlidingViewSwitcher。
新建一個(gè)類,名叫SlidingSwitcherView,這個(gè)類是繼承自RelativeLayout的,并且實(shí)現(xiàn)了OnTouchListener接口,具體代碼如下:
public class SlidingSwitcherView extends RelativeLayout implements OnTouchListener { /** * 讓菜單滾動(dòng),手指滑動(dòng)需要達(dá)到的速度。 */ public static final int SNAP_VELOCITY = 200; /** * SlidingSwitcherView的寬度。 */ private int switcherViewWidth; /** * 當(dāng)前顯示的元素的下標(biāo)。 */ private int currentItemIndex; /** * 菜單中包含的元素總數(shù)。 */ private int itemsCount; /** * 各個(gè)元素的偏移邊界值。 */ private int[] borders; /** * 最多可以滑動(dòng)到的左邊緣。值由菜單中包含的元素總數(shù)來(lái)定,marginLeft到達(dá)此值之后,不能再減少。 * */ private int leftEdge = 0; /** * 最多可以滑動(dòng)到的右邊緣。值恒為0,marginLeft到達(dá)此值之后,不能再增加。 */ private int rightEdge = 0; /** * 記錄手指按下時(shí)的橫坐標(biāo)。 */ private float xDown; /** * 記錄手指移動(dòng)時(shí)的橫坐標(biāo)。 */ private float xMove; /** * 記錄手機(jī)抬起時(shí)的橫坐標(biāo)。 */ private float xUp; /** * 菜單布局。 */ private LinearLayout itemsLayout; /** * 標(biāo)簽布局。 */ private LinearLayout dotsLayout; /** * 菜單中的第一個(gè)元素。 */ private View firstItem; /** * 菜單中第一個(gè)元素的布局,用于改變leftMargin的值,來(lái)決定當(dāng)前顯示的哪一個(gè)元素。 */ private MarginLayoutParams firstItemParams; /** * 用于計(jì)算手指滑動(dòng)的速度。 */ private VelocityTracker mVelocityTracker; /** * 重寫SlidingSwitcherView的構(gòu)造函數(shù),用于允許在XML中引用當(dāng)前的自定義布局。 * * @param context * @param attrs */ public SlidingSwitcherView(Context context, AttributeSet attrs) { super(context, attrs); } /** * 滾動(dòng)到下一個(gè)元素。 */ public void scrollToNext() { new ScrollTask().execute(-20); } /** * 滾動(dòng)到上一個(gè)元素。 */ public void scrollToPrevious() { new ScrollTask().execute(20); } /** * 在onLayout中重新設(shè)定菜單元素和標(biāo)簽元素的參數(shù)。 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed) { initializeItems(); initializeDots(); } } /** * 初始化菜單元素,為每一個(gè)子元素增加監(jiān)聽事件,并且改變所有子元素的寬度,讓它們等于父元素的寬度。 */ private void initializeItems() { switcherViewWidth = getWidth(); itemsLayout = (LinearLayout) getChildAt(0); itemsCount = itemsLayout.getChildCount(); borders = new int[itemsCount]; for (int i = 0; i < itemsCount; i++) { borders[i] = -i * switcherViewWidth; View item = itemsLayout.getChildAt(i); MarginLayoutParams params = (MarginLayoutParams) item.getLayoutParams(); params.width = switcherViewWidth; item.setLayoutParams(params); item.setOnTouchListener(this); } leftEdge = borders[itemsCount - 1]; firstItem = itemsLayout.getChildAt(0); firstItemParams = (MarginLayoutParams) firstItem.getLayoutParams(); } /** * 初始化標(biāo)簽元素。 */ private void initializeDots() { dotsLayout = (LinearLayout) getChildAt(1); refreshDotsLayout(); } @Override public boolean onTouch(View v, MotionEvent event) { createVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 手指按下時(shí),記錄按下時(shí)的橫坐標(biāo) xDown = event.getRawX(); break; case MotionEvent.ACTION_MOVE: // 手指移動(dòng)時(shí),對(duì)比按下時(shí)的橫坐標(biāo),計(jì)算出移動(dòng)的距離,來(lái)調(diào)整左側(cè)布局的leftMargin值,從而顯示和隱藏左側(cè)布局 xMove = event.getRawX(); int distanceX = (int) (xMove - xDown) - (currentItemIndex * switcherViewWidth); firstItemParams.leftMargin = distanceX; if (beAbleToScroll()) { firstItem.setLayoutParams(firstItemParams); } break; case MotionEvent.ACTION_UP: // 手指抬起時(shí),進(jìn)行判斷當(dāng)前手勢(shì)的意圖,從而決定是滾動(dòng)到左側(cè)布局,還是滾動(dòng)到右側(cè)布局 xUp = event.getRawX(); if (beAbleToScroll()) { if (wantScrollToPrevious()) { if (shouldScrollToPrevious()) { currentItemIndex--; scrollToPrevious(); refreshDotsLayout(); } else { scrollToNext(); } } else if (wantScrollToNext()) { if (shouldScrollToNext()) { currentItemIndex++; scrollToNext(); refreshDotsLayout(); } else { scrollToPrevious(); } } } recycleVelocityTracker(); break; } return false; } /** * 當(dāng)前是否能夠滾動(dòng),滾動(dòng)到第一個(gè)或最后一個(gè)元素時(shí)將不能再滾動(dòng)。 * * @return 當(dāng)前l(fā)eftMargin的值在leftEdge和rightEdge之間返回true,否則返回false。 */ private boolean beAbleToScroll() { return firstItemParams.leftMargin < rightEdge && firstItemParams.leftMargin > leftEdge; } /** * 判斷當(dāng)前手勢(shì)的意圖是不是想滾動(dòng)到上一個(gè)菜單元素。如果手指移動(dòng)的距離是正數(shù),則認(rèn)為當(dāng)前手勢(shì)是想要滾動(dòng)到上一個(gè)菜單元素。 * * @return 當(dāng)前手勢(shì)想滾動(dòng)到上一個(gè)菜單元素返回true,否則返回false。 */ private boolean wantScrollToPrevious() { return xUp - xDown > 0; } /** * 判斷當(dāng)前手勢(shì)的意圖是不是想滾動(dòng)到下一個(gè)菜單元素。如果手指移動(dòng)的距離是負(fù)數(shù),則認(rèn)為當(dāng)前手勢(shì)是想要滾動(dòng)到下一個(gè)菜單元素。 * * @return 當(dāng)前手勢(shì)想滾動(dòng)到下一個(gè)菜單元素返回true,否則返回false。 */ private boolean wantScrollToNext() { return xUp - xDown < 0; } /** * 判斷是否應(yīng)該滾動(dòng)到下一個(gè)菜單元素。如果手指移動(dòng)距離大于屏幕的1/2,或者手指移動(dòng)速度大于SNAP_VELOCITY, * 就認(rèn)為應(yīng)該滾動(dòng)到下一個(gè)菜單元素。 * * @return 如果應(yīng)該滾動(dòng)到下一個(gè)菜單元素返回true,否則返回false。 */ private boolean shouldScrollToNext() { return xDown - xUp > switcherViewWidth / 2 || getScrollVelocity() > SNAP_VELOCITY; } /** * 判斷是否應(yīng)該滾動(dòng)到上一個(gè)菜單元素。如果手指移動(dòng)距離大于屏幕的1/2,或者手指移動(dòng)速度大于SNAP_VELOCITY, * 就認(rèn)為應(yīng)該滾動(dòng)到上一個(gè)菜單元素。 * * @return 如果應(yīng)該滾動(dòng)到上一個(gè)菜單元素返回true,否則返回false。 */ private boolean shouldScrollToPrevious() { return xUp - xDown > switcherViewWidth / 2 || getScrollVelocity() > SNAP_VELOCITY; } /** * 刷新標(biāo)簽元素布局,每次currentItemIndex值改變的時(shí)候都應(yīng)該進(jìn)行刷新。 */ private void refreshDotsLayout() { dotsLayout.removeAllViews(); for (int i = 0; i < itemsCount; i++) { LinearLayout.LayoutParams linearParams = new LinearLayout.LayoutParams(0, LayoutParams.FILL_PARENT); linearParams.weight = 1; RelativeLayout relativeLayout = new RelativeLayout(getContext()); ImageView image = new ImageView(getContext()); if (i == currentItemIndex) { image.setBackgroundResource(R.drawable.dot_selected); } else { image.setBackgroundResource(R.drawable.dot_unselected); } RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); relativeParams.addRule(RelativeLayout.CENTER_IN_PARENT); relativeLayout.addView(image, relativeParams); dotsLayout.addView(relativeLayout, linearParams); } } /** * 創(chuàng)建VelocityTracker對(duì)象,并將觸摸事件加入到VelocityTracker當(dāng)中。 * * @param event * 右側(cè)布局監(jiān)聽控件的滑動(dòng)事件 */ private void createVelocityTracker(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** * 獲取手指在右側(cè)布局的監(jiān)聽View上的滑動(dòng)速度。 * * @return 滑動(dòng)速度,以每秒鐘移動(dòng)了多少像素值為單位。 */ private int getScrollVelocity() { mVelocityTracker.computeCurrentVelocity(1000); int velocity = (int) mVelocityTracker.getXVelocity(); return Math.abs(velocity); } /** * 回收VelocityTracker對(duì)象。 */ private void recycleVelocityTracker() { mVelocityTracker.recycle(); mVelocityTracker = null; } /** * 檢測(cè)菜單滾動(dòng)時(shí),是否有穿越border,border的值都存儲(chǔ)在{@link #borders}中。 * * @param leftMargin * 第一個(gè)元素的左偏移值 * @param speed * 滾動(dòng)的速度,正數(shù)說(shuō)明向右滾動(dòng),負(fù)數(shù)說(shuō)明向左滾動(dòng)。 * @return 穿越任何一個(gè)border了返回true,否則返回false。 */ private boolean isCrossBorder(int leftMargin, int speed) { for (int border : borders) { if (speed > 0) { if (leftMargin >= border && leftMargin - speed < border) { return true; } } else { if (leftMargin <= border && leftMargin - speed > border) { return true; } } } return false; } /** * 找到離當(dāng)前的leftMargin最近的一個(gè)border值。 * * @param leftMargin * 第一個(gè)元素的左偏移值 * @return 離當(dāng)前的leftMargin最近的一個(gè)border值。 */ private int findClosestBorder(int leftMargin) { int absLeftMargin = Math.abs(leftMargin); int closestBorder = borders[0]; int closestMargin = Math.abs(Math.abs(closestBorder) - absLeftMargin); for (int border : borders) { int margin = Math.abs(Math.abs(border) - absLeftMargin); if (margin < closestMargin) { closestBorder = border; closestMargin = margin; } } return closestBorder; } class ScrollTask extends AsyncTask<Integer, Integer, Integer> { @Override protected Integer doInBackground(Integer... speed) { int leftMargin = firstItemParams.leftMargin; // 根據(jù)傳入的速度來(lái)滾動(dòng)界面,當(dāng)滾動(dòng)穿越border時(shí),跳出循環(huán)。 while (true) { leftMargin = leftMargin + speed[0]; if (isCrossBorder(leftMargin, speed[0])) { leftMargin = findClosestBorder(leftMargin); break; } publishProgress(leftMargin); // 為了要有滾動(dòng)效果產(chǎn)生,每次循環(huán)使線程睡眠10毫秒,這樣肉眼才能夠看到滾動(dòng)動(dòng)畫。 sleep(10); } return leftMargin; } @Override protected void onProgressUpdate(Integer... leftMargin) { firstItemParams.leftMargin = leftMargin[0]; firstItem.setLayoutParams(firstItemParams); } @Override protected void onPostExecute(Integer leftMargin) { firstItemParams.leftMargin = leftMargin; firstItem.setLayoutParams(firstItemParams); } } /** * 使當(dāng)前線程睡眠指定的毫秒數(shù)。 * * @param millis * 指定當(dāng)前線程睡眠多久,以毫秒為單位 */ private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } }
細(xì)心的朋友可以看出來(lái),我還是重用了很多之前的代碼,這里有幾個(gè)重要點(diǎn)我說(shuō)一下。在onLayout方法里,重定義了各個(gè)包含圖片的控件的大小,然后為每個(gè)包含圖片的控件都注冊(cè)了一個(gè)touch事件監(jiān)聽器。這樣當(dāng)我們滑動(dòng)任何一樣圖片控件的時(shí)候,都會(huì)觸發(fā)onTouch事件,然后通過(guò)改變第一個(gè)圖片控件的leftMargin,去實(shí)現(xiàn)動(dòng)畫效果。之后在onLayout里又動(dòng)態(tài)加入了頁(yè)簽View,有幾個(gè)圖片控件就會(huì)加入幾個(gè)頁(yè)簽,然后根據(jù)currentItemIndex來(lái)決定高亮顯示哪一個(gè)頁(yè)簽。其它也沒什么要特別說(shuō)明的了,更深的理解大家去看代碼和注釋吧。
然后看一下布局文件中如何使用我們自定義的這個(gè)控件,創(chuàng)建或打開activity_main.xml,里面加入如下代碼:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" tools:context=".MainActivity" > <com.example.viewswitcher.SlidingSwitcherView android:id="@+id/slidingLayout" android:layout_width="fill_parent" android:layout_height="100dip" > <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/image1" /> <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/image2" /> <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/image3" /> <Button android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/image4" /> </LinearLayout> <LinearLayout android:layout_width="60dip" android:layout_height="20dip" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:layout_margin="15dip" android:orientation="horizontal" > </LinearLayout> </com.example.viewswitcher.SlidingSwitcherView> </LinearLayout>
我們可以看到,com.example.viewswitcher.SlidingSwitcherView的根目錄下放置了兩個(gè)LinearLayout。第一個(gè)LinearLayout中要放入需要滾動(dòng)顯示的圖片,這里我們加入了四個(gè)Button,每個(gè)Button都設(shè)置了一張背景圖片。第二個(gè)LinearLayout中不需要加入任何東西,只要控制好大小和位置,標(biāo)簽會(huì)在運(yùn)行的時(shí)候自動(dòng)加入到這個(gè)layout中。
然后創(chuàng)建或打開MainActivity作為主界面,里面沒有加入任何新增的代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
最后是給出AndroidManifest.xml的代碼,也都是自動(dòng)生成的內(nèi)容:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.viewswitcher" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar" > <activity android:name="com.example.viewswitcher.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
好了,現(xiàn)在我們來(lái)看下運(yùn)行效果吧,由于手機(jī)壞了,只能在模擬器上運(yùn)行了。
首先是程序打開的時(shí)候,界面顯示如下:
然后手指在圖片上滑動(dòng),我們可以看到圖片滾動(dòng)的效果:
不停的翻頁(yè),頁(yè)簽也會(huì)跟著一起改變,下圖中我們可以看到高亮顯示的點(diǎn)是變換的:
恩,對(duì)比一下淘寶客戶端的效果,我覺得我們模仿的還是挺好的。咦,好像少了點(diǎn)什么。。。。。。原來(lái)圖片并不會(huì)自動(dòng)播放。。。。。
沒關(guān)系,我在后面的一篇文章中補(bǔ)充了自動(dòng)播放這個(gè)功能,而且不僅僅是自動(dòng)播放功能喔,請(qǐng)參考 Android使用自定義屬性實(shí)現(xiàn)圖片自動(dòng)播放滾動(dòng)的功能。
今天的文章就到這里了,有問(wèn)題的朋友請(qǐng)?jiān)谙旅媪粞浴?/p>
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- Android高級(jí)圖片滾動(dòng)控件實(shí)現(xiàn)3D版圖片輪播器
- Android使用自定義屬性實(shí)現(xiàn)圖片自動(dòng)播放滾動(dòng)的功能
- Android使用Recyclerview實(shí)現(xiàn)圖片水平自動(dòng)循環(huán)滾動(dòng)效果
- Android_RecyclerView實(shí)現(xiàn)上下滾動(dòng)廣告條實(shí)例(帶圖片)
- Android組件Glide實(shí)現(xiàn)圖片平滑滾動(dòng)效果
- Android仿淘寶商品瀏覽界面圖片滾動(dòng)效果
- Android程序開發(fā)ListView+Json+異步網(wǎng)絡(luò)圖片加載+滾動(dòng)翻頁(yè)的例子(圖片能緩存,圖片不錯(cuò)亂)
- Android實(shí)現(xiàn)圖片滾動(dòng)效果
相關(guān)文章
Android studio實(shí)現(xiàn)菜單效果
這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)菜單效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Android實(shí)現(xiàn)簡(jiǎn)潔的APP更新dialog數(shù)字進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)潔的APP更新dialog數(shù)字進(jìn)度條,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Android App開發(fā)中創(chuàng)建Fragment組件的教程
這篇文章主要介紹了Android App開發(fā)中創(chuàng)建Fragment的教程,Fragment是用以更靈活地構(gòu)建多屏幕界面的可UI組件,需要的朋友可以參考下2016-05-05Android初學(xué)者必須知道的10個(gè)技術(shù)
本篇內(nèi)容給大家整理10個(gè)作為Android初學(xué)者必須要了解和會(huì)用的技術(shù)以及詳細(xì)代碼分析,需要的朋友收藏下慢慢學(xué)習(xí)吧。2017-12-12Android入門之讀寫本地文件的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android如何實(shí)現(xiàn)讀寫本地文件的功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Android有一定的幫助,需要的可以參考一下2022-12-12Android簡(jiǎn)易電話撥號(hào)器實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了Android簡(jiǎn)易電話撥號(hào)器實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android Spinner 組件的應(yīng)用實(shí)例
這篇文章主要介紹了Android Spinner 組件的應(yīng)用實(shí)例的相關(guān)資料,希望通過(guò)本文大家能夠掌握這部分內(nèi)容,需要的朋友可以參考下2017-09-09android基于dialog實(shí)現(xiàn)等待加載框示例
本篇文章主要介紹了android基于dialog實(shí)現(xiàn)等待加載框示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02