Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android系統教程 >> Android開發教程 >> Android實現圖片滾動控件,含頁簽功能,讓你的應用像淘寶一樣炫起來

Android實現圖片滾動控件,含頁簽功能,讓你的應用像淘寶一樣炫起來

編輯:Android開發教程

首先題外話,今天早上起床的時候,手滑一下把我的手機甩了出去,結果陪伴我兩年半的摩托羅拉裡程碑 一代就這麼安息了,於是我今天決定怒更一記,紀念我死去的愛機。

如果你是網購達人,你的手機上 一定少不了淘寶客戶端。關注特效的人一定都會發現,淘寶不管是網站還是手機客戶端,主頁上都會有一個 圖片滾動播放器,上面展示一些它推薦的商品。這個幾乎可以用淘寶來冠名的功能,看起來還是挺炫的,我 們今天就來實現一下。

實現原理其實還是之前那篇文章Android滑動菜單特效實現,仿人人客戶端側 滑效果,史上最簡單的側滑實現  ,算是以那個原理為基礎的另外一個變種。正所謂一通百通,真正掌 握一種方法之後,就可以使用這個方法變換出各種不通的效果。

今天仍然還是實現一個自定義控件, 然後我們在任意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的值都存儲在{@link #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();  
        }  
    }  
}

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved