編輯:Android開發實例
如果你是網購達人,你的手機上一定少不了淘寶客戶端。關注特效的人一定都會發現,淘寶不管是網站還是手機客戶端,主頁上都會有一個圖片滾動播放器,上面展示一些它推薦的商品。這個幾乎可以用淘寶來冠名的功能,看起來還是挺炫的,我們今天就來實現一下。
實現原理其實還是之前那篇文章http://www.fengfly.com/plus/view-215082-1.html ,算是以那個原理為基礎的另外一個變種。正所謂一通百通,真正掌握一種方法之後,就可以使用這個方法變換出各種不通的效果。
今天仍然還是實現一個自定義控件,然後我們在任意Activity的布局文件中引用一下,即可實現圖片滾動器的效果。
在Eclipse中新建一個Android項目,項目名就叫做SlidingViewSwitcher。
新建一個類,名叫SlidingSwitcherView,這個類是繼承自RelativeLayout的,並且實現了OnTouchListener接口,具體代碼如下:
- public class SlidingSwitcherView extends RelativeLayout implements OnTouchListener {
- /**
- * 讓菜單滾動,手指滑動需要達到的速度。
- */
- public static final int SNAP_VELOCITY = 200;
- /**
- * SlidingSwitcherView的寬度。
- */
- private int switcherViewWidth;
- /**
- * 當前顯示的元素的下標。
- */
- private int currentItemIndex;
- /**
- * 菜單中包含的元素總數。
- */
- private int itemsCount;
- /**
- * 各個元素的偏移邊界值。
- */
- private int[] borders;
- /**
- * 最多可以滑動到的左邊緣。值由菜單中包含的元素總數來定,marginLeft到達此值之後,不能再減少。
- *
- */
- private int leftEdge = 0;
- /**
- * 最多可以滑動到的右邊緣。值恆為0,marginLeft到達此值之後,不能再增加。
- */
- private int rightEdge = 0;
- /**
- * 記錄手指按下時的橫坐標。
- */
- private float xDown;
- /**
- * 記錄手指移動時的橫坐標。
- */
- private float xMove;
- /**
- * 記錄手機抬起時的橫坐標。
- */
- private float xUp;
- /**
- * 菜單布局。
- */
- private LinearLayout itemsLayout;
- /**
- * 標簽布局。
- */
- private LinearLayout dotsLayout;
- /**
- * 菜單中的第一個元素。
- */
- private View firstItem;
- /**
- * 菜單中第一個元素的布局,用於改變leftMargin的值,來決定當前顯示的哪一個元素。
- */
- private MarginLayoutParams firstItemParams;
- /**
- * 用於計算手指滑動的速度。
- */
- private VelocityTracker mVelocityTracker;
- /**
- * 重寫SlidingSwitcherView的構造函數,用於允許在XML中引用當前的自定義布局。
- *
- * @param context
- * @param attrs
- */
- public SlidingSwitcherView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- /**
- * 滾動到下一個元素。
- */
- public void scrollToNext() {
- new ScrollTask().execute(-20);
- }
- /**
- * 滾動到上一個元素。
- */
- public void scrollToPrevious() {
- new ScrollTask().execute(20);
- }
- /**
- * 在onLayout中重新設定菜單元素和標簽元素的參數。
- */
- @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();
- }
- }
- /**
- * 初始化菜單元素,為每一個子元素增加監聽事件,並且改變所有子元素的寬度,讓它們等於父元素的寬度。
- */
- 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();
- }
- /**
- * 初始化標簽元素。
- */
- 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:
- // 手指按下時,記錄按下時的橫坐標
- xDown = event.getRawX();
- break;
- case MotionEvent.ACTION_MOVE:
- // 手指移動時,對比按下時的橫坐標,計算出移動的距離,來調整左側布局的leftMargin值,從而顯示和隱藏左側布局
- xMove = event.getRawX();
- int distanceX = (int) (xMove - xDown) - (currentItemIndex * switcherViewWidth);
- firstItemParams.leftMargin = distanceX;
- if (beAbleToScroll()) {
- firstItem.setLayoutParams(firstItemParams);
- }
- break;
- case MotionEvent.ACTION_UP:
- // 手指抬起時,進行判斷當前手勢的意圖,從而決定是滾動到左側布局,還是滾動到右側布局
- 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;
- }
- /**
- * 當前是否能夠滾動,滾動到第一個或最後一個元素時將不能再滾動。
- *
- * @return 當前leftMargin的值在leftEdge和rightEdge之間返回true,否則返回false。
- */
- private boolean beAbleToScroll() {
- return firstItemParams.leftMargin < rightEdge && firstItemParams.leftMargin > leftEdge;
- }
- /**
- * 判斷當前手勢的意圖是不是想滾動到上一個菜單元素。如果手指移動的距離是正數,則認為當前手勢是想要滾動到上一個菜單元素。
- *
- * @return 當前手勢想滾動到上一個菜單元素返回true,否則返回false。
- */
- private boolean wantScrollToPrevious() {
- return xUp - xDown > 0;
- }
- /**
- * 判斷當前手勢的意圖是不是想滾動到下一個菜單元素。如果手指移動的距離是負數,則認為當前手勢是想要滾動到下一個菜單元素。
- *
- * @return 當前手勢想滾動到下一個菜單元素返回true,否則返回false。
- */
- private boolean wantScrollToNext() {
- return xUp - xDown < 0;
- }
- /**
- * 判斷是否應該滾動到下一個菜單元素。如果手指移動距離大於屏幕的1/2,或者手指移動速度大於SNAP_VELOCITY,
- * 就認為應該滾動到下一個菜單元素。
- *
- * @return 如果應該滾動到下一個菜單元素返回true,否則返回false。
- */
- private boolean shouldScrollToNext() {
- return xDown - xUp > switcherViewWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
- }
- /**
- * 判斷是否應該滾動到上一個菜單元素。如果手指移動距離大於屏幕的1/2,或者手指移動速度大於SNAP_VELOCITY,
- * 就認為應該滾動到上一個菜單元素。
- *
- * @return 如果應該滾動到上一個菜單元素返回true,否則返回false。
- */
- private boolean shouldScrollToPrevious() {
- return xUp - xDown > switcherViewWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
- }
- /**
- * 刷新標簽元素布局,每次currentItemIndex值改變的時候都應該進行刷新。
- */
- 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);
- }
- }
- /**
- * 創建VelocityTracker對象,並將觸摸事件加入到VelocityTracker當中。
- *
- * @param event
- * 右側布局監聽控件的滑動事件
- */
- private void createVelocityTracker(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- }
- /**
- * 獲取手指在右側布局的監聽View上的滑動速度。
- *
- * @return 滑動速度,以每秒鐘移動了多少像素值為單位。
- */
- private int getScrollVelocity() {
- mVelocityTracker.computeCurrentVelocity(1000);
- int velocity = (int) mVelocityTracker.getXVelocity();
- return Math.abs(velocity);
- }
- /**
- * 回收VelocityTracker對象。
- */
- private void recycleVelocityTracker() {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- /**
- * 檢測菜單滾動時,是否有穿越border,border的值都存儲在[email protected] #borders}中。
- *
- * @param leftMargin
- * 第一個元素的左偏移值
- * @param speed
- * 滾動的速度,正數說明向右滾動,負數說明向左滾動。
- * @return 穿越任何一個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;
- }
- /**
- * 找到離當前的leftMargin最近的一個border值。
- *
- * @param leftMargin
- * 第一個元素的左偏移值
- * @return 離當前的leftMargin最近的一個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;
- // 根據傳入的速度來滾動界面,當滾動穿越border時,跳出循環。
- while (true) {
- leftMargin = leftMargin + speed[0];
- if (isCrossBorder(leftMargin, speed[0])) {
- leftMargin = findClosestBorder(leftMargin);
- break;
- }
- publishProgress(leftMargin);
- // 為了要有滾動效果產生,每次循環使線程睡眠10毫秒,這樣肉眼才能夠看到滾動動畫。
- 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);
- }
- }
- /**
- * 使當前線程睡眠指定的毫秒數。
- *
- * @param millis
- * 指定當前線程睡眠多久,以毫秒為單位
- */
- private void sleep(long millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
細心的朋友可以看出來,我還是重用了很多之前的代碼,這裡有幾個重要點我說一下。在onLayout方法裡,重定義了各個包含圖片的控件的大小,然後為每個包含圖片的控件都注冊了一個touch事件監聽器。這樣當我們滑動任何一樣圖片控件的時候,都會觸發onTouch事件,然後通過改變第一個圖片控件的leftMargin,去實現動畫效果。之後在onLayout裡又動態加入了頁簽View,有幾個圖片控件就會加入幾個頁簽,然後根據currentItemIndex來決定高亮顯示哪一個頁簽。其它也沒什麼要特別說明的了,更深的理解大家去看代碼和注釋吧。
然後看一下布局文件中如何使用我們自定義的這個控件,創建或打開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的根目錄下放置了兩個LinearLayout。第一個LinearLayout中要放入需要滾動顯示的圖片,這裡我們加入了四個Button,每個Button都設置了一張背景圖片。第二個LinearLayout中不需要加入任何東西,只要控制好大小和位置,標簽會在運行的時候自動加入到這個layout中。
然後創建或打開MainActivity作為主界面,裡面沒有加入任何新增的代碼:
- public class MainActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- }
- }
最後是給出AndroidManifest.xml的代碼,也都是自動生成的內容:
- <?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>
好了,現在我們來看下運行效果吧,由於手機壞了,只能在模擬器上運行了。
首先是程序打開的時候,界面顯示如下:
然後手指在圖片上滑動,我們可以看到圖片滾動的效果:
不停的翻頁,頁簽也會跟著一起改變,下圖中我們可以看到高亮顯示的點是變換的:
恩,對比一下淘寶客戶端的效果,我覺得我們模仿的還是挺好的。咦,好像少了點什麼。。。。。。原來圖片並不會自動播放。。。。。
沒關系,我在後面的一篇文章中補充了自動播放這個功能,而且不僅僅是自動播放功能喔,請繼續關注。
源碼下載,請點擊這裡
登錄應用程序的屏幕,詢問憑據登錄到一些特定的應用。可能需要登錄到Facebook,微博等本章介紹了,如何創建一個登錄界面,以及如何管理安全問題和錯誤嘗試。首先,必須定義兩
Android中可以直接在位圖上進行人臉檢測。Android SDK為人臉檢測
Android提供了許多方法來控制播放的音頻/視頻文件和流。其中該方法是通過一類稱為MediaPlayer。Android是提供MediaPlayer類訪問內置的媒體播放
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我