android 循環自定義View

效果:固定的子view個數,循環滑動,第一個子View向左滑即滑到最後一個子View,最後一個子View向後右滑即滑到第一個子View

效果圖如下:


佈局:可以看出LoopViewGroup實現循環滑動,包括七張ImageView 圖片

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/btn_remove"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="remove" />

    <com.example.loop.LoopViewGroup
        android:id="@+id/loop_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:padding="50dp" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image1"
            android:tag="image1" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image2"
            android:tag="image2" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image3"
            android:tag="image3" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image4"
            android:tag="image4" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image5"
            android:tag="image5" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image6"
            android:tag="image6" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/image7"
            android:tag="image7" />
    </com.example.loop.LoopViewGroup>

</RelativeLayout>

首先說一說ViewGroup中方法setDescendantFocusability的作用

有三個參數FOCUS_BEFORE_DESCENDANTS,FOCUS_AFTER_DESCENDANTS,FOCUS_BLOCK_DESCENDANTS,此例中會使用FOCUS_BLOCK_DESCENDANTS這個參數。先看ViewGroup源碼中的調用:

/**
     * {@inheritDoc}
     *
     * Looks for a view to give focus to respecting the setting specified by
     * {@link #getDescendantFocusability()}.
     *
     * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to
     * find focus within the children of this group when appropriate.
     *
     * @see #FOCUS_BEFORE_DESCENDANTS
     * @see #FOCUS_AFTER_DESCENDANTS
     * @see #FOCUS_BLOCK_DESCENDANTS
     * @see #onRequestFocusInDescendants(int, android.graphics.Rect) 
     */
    @Override
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        if (DBG) {
            System.out.println(this + " ViewGroup.requestFocus direction="
                    + direction);
        }
        int descendantFocusability = getDescendantFocusability();

        switch (descendantFocusability) {
            case FOCUS_BLOCK_DESCENDANTS:
                return super.requestFocus(direction, previouslyFocusedRect);
            case FOCUS_BEFORE_DESCENDANTS: {
                final boolean took = super.requestFocus(direction, previouslyFocusedRect);
                return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect);
            }
            case FOCUS_AFTER_DESCENDANTS: {
                final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
            }
            default:
                throw new IllegalStateException("descendant focusability must be "
                        + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS "
                        + "but is " + descendantFocusability);
        }
    }

FOCUS_BLOCK_DESCENDANTS參數不會傳遞到子View,FOCUS_BEFORE_DESCENDANTS先判斷當前是否能獲取焦點,否則就傳遞給子View,FOCUS_AFTER_DESCENDANTS則相反,先判斷子View是否可以獲取焦點。


LoopViewGroup的代碼:

package com.example.loop;

import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.OnHierarchyChangeListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.Scroller;

public class LoopViewGroup extends ViewGroup implements View.OnFocusChangeListener, OnHierarchyChangeListener {
	public static final int DIRECTION_BACKWARD = 0;
	public static final int DIRECTION_FORWARD = 1;

	private int mDirection = DIRECTION_FORWARD;
	private List<View> mImages = new ArrayList<View>();
	private int mCurIndex = 0;
	private int mPreIndex = 0;

	protected Scroller mScroller;

	private int mItemWidth;
	private int mItemheight;

	private OnSelectedListener mListener;

	public LoopViewGroup(Context context) {
		this(context, null);
	}

	public LoopViewGroup(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	//
	public LoopViewGroup(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		setChildrenDrawingOrderEnabled(true);
		setFocusable(true);
		setFocusableInTouchMode(true);
		setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
		// view的添加和刪除監聽
		setOnHierarchyChangeListener(this);
		// 允許在dispatchDraw和onDraw中畫
		setWillNotDraw(false);
		setOnFocusChangeListener(this);

		final Resources res = getResources();
		mItemWidth = res.getDimensionPixelSize(R.dimen.item_width);
		mItemheight = res.getDimensionPixelSize(R.dimen.item_height);

		mScroller = new Scroller(getContext(), new DecelerateInterpolator());
		scrollTo(0, 0);
	}

	public void setOnSelectedListener(OnSelectedListener l) {
		mListener = l;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

		int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

		int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
		int count = getChildCount();
		int newWidth = getPaddingLeft() + getPaddingRight();
		int newHeight = getPaddingTop() + getPaddingBottom() + mItemheight;
		for (int i = 0; i < count; i++) {
			View child = getChildAt(i);
			child.measure(MeasureSpec.makeMeasureSpec(mItemWidth, MeasureSpec.EXACTLY),
					MeasureSpec.makeMeasureSpec(mItemheight, MeasureSpec.EXACTLY));
			measureChild(child, widthMeasureSpec, heightMeasureSpec);
			newWidth += mItemWidth;
		}
		setMeasuredDimension(Math.min(newWidth, widthSpecSize), Math.min(newHeight, heightSpecSize));
	}

	// 根據方向以及當前的選中和先前的選中layout子view
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int count = mImages.size();
		int scrollX = getScrollX();
		int paddingLeft = getPaddingLeft();
		int paddingTop = getPaddingTop();
		int left = scrollX + paddingLeft;
		int right = 0;
		int top = paddingTop;
		int bottom = 0;
		int startIndex = 0;
		if (mCurIndex == mPreIndex) {
			startIndex = mCurIndex;
		} else if (mDirection == DIRECTION_BACKWARD) {
			left += -(mItemWidth);
			startIndex = mCurIndex;
		} else {
			startIndex = mCurIndex - 1 < 0 ? count - 1 : mCurIndex - 1;
		}
		for (int i = startIndex; i < count + startIndex - 1; i++) {
			int index = i;
			if (i >= count) {
				index = i - count;
			}
			View child = mImages.get(index);
			right = left + mItemWidth;
			top = paddingTop;
			bottom = top + mItemheight;
			child.layout(left, top, right, bottom);
			left += mItemWidth;
		}
	}

	// 設置選中的index
	public void setIndex(int index, int direction) {
		int size = mImages.size();
		if (index >= 0 && index < size) {
			mPreIndex = mCurIndex;
			mCurIndex = index;
			// 調用選中監聽
			if (mListener != null) {
				mListener.onSelectedChange(mImages.get(mCurIndex), mCurIndex);
			}
			if (mCurIndex == mPreIndex) {
				return;
			} else {
				// 開始滑動動畫
				mDirection = direction;
				if (!mScroller.isFinished()) {
					mScroller.abortAnimation();
					scrollTo(mScroller.getFinalX(), mScroller.getFinalY());
				}
				requestLayout();
				int scrollX = getScrollX();
				int dx;
				if (mDirection == DIRECTION_BACKWARD) {
					dx = -mItemWidth;
				} else {
					dx = mItemWidth;
				}
				mScroller.startScroll(scrollX, 0, dx, 0, 400);
				invalidate();
			}
		} else {
			if (mListener != null) {
				mListener.onNothingSelected();
			}
		}
	}

	// 獲取選中的view
	public View getSelectedView() {
		int count = mImages.size();
		if (mCurIndex >= 0 && mCurIndex < count) {
			return mImages.get(mCurIndex);
		}
		return null;
	}

	// 畫出焦點框
	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);

		if (isFocused()) {
			Drawable d = getResources().getDrawable(R.drawable.item_border);
			Rect padding = new Rect();
			d.getPadding(padding);
			int left = getPaddingLeft() + getScrollX() - padding.left;
			int top = getPaddingTop() + getScrollY() - padding.top;
			int right = left + mItemWidth + padding.left + padding.right;
			int bottom = top + mItemheight + padding.top + padding.bottom;
			d.setBounds(left, top, right, bottom);
			d.draw(canvas);
		}
	}

	// 左右移動時判斷哪一個view選中,return true
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			int size = mImages.size();
			final int curIndex = mCurIndex;
			if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
				if (curIndex > 0) {
					setIndex(curIndex - 1, DIRECTION_BACKWARD);
				} else {
					setIndex(size - 1, DIRECTION_BACKWARD);
				}
			} else {
				if (curIndex < size - 1) {
					setIndex(curIndex + 1, DIRECTION_FORWARD);
				} else {
					setIndex(0, DIRECTION_FORWARD);
				}
			}
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	// 當前view的焦點變化,重繪出焦點框
	@Override
	public void onFocusChange(View view, boolean hasFocus) {
		setIndex(mCurIndex, DIRECTION_FORWARD);
		invalidate();
	}

	// 子view添加
	@Override
	public void onChildViewAdded(View parent, View child) {
		mImages.add(child);
	}

	// 子view移除
	@Override
	public void onChildViewRemoved(View parent, View child) {
		int index = mImages.indexOf(child);
		if (index >= 0) {
			mImages.remove(child);
			setIndex(Math.min(mCurIndex, mImages.size() - 1), DIRECTION_FORWARD);
		}
	}

	// 判斷滑動動畫是否完成
	@Override
	public void computeScroll() {
		super.computeScroll();
		if (mScroller.computeScrollOffset()) {
			if (getScrollX() != mScroller.getCurrX() || getScrollY() != mScroller.getCurrY()) {
				scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			}
			invalidate();
		} else if (mScroller.isFinished()) {

		}
	}

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		mImages.clear();
		mListener = null;
	}

	static class ScrollInterpolator implements Interpolator {
		public ScrollInterpolator() {
		}

		public float getInterpolation(float t) {
			t -= 1.0f;
			return t * t * t * t * t + 1;
		}
	}

	// 監聽view的選中
	public interface OnSelectedListener {
		public void onSelectedChange(View v, int index);

		public void onNothingSelected();
	}

}

在MainActivity中的使用:

package com.example.loop;

import com.example.loop.LoopViewGroup.OnSelectedListener;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.app.Activity;

public class MainActivity extends Activity implements OnClickListener, OnSelectedListener {

	public static final String TAG = "Loop MainActivity";

	LoopViewGroup mLoopViewGroup;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		mLoopViewGroup = (LoopViewGroup) findViewById(R.id.loop_view);
		mLoopViewGroup.setOnSelectedListener(this);
		//findViewById(R.id.btn_remove).setOnClickListener(this);
	}

	@Override
	public void onSelectedChange(View v, int index) {
		Log.d(TAG, "onSelectedChange tag : " + v.getTag().toString() + "  index = " + index);
	}

	@Override
	public void onNothingSelected() {
		Log.d(TAG, "onNothingSelected");
	}

	@Override
	public void onClick(View v) {

		if (v == null) {
			return;
		}

		int id = v.getId();
		switch (id) {
		case R.id.btn_remove:
			// Log.d(TAG, "btn remove click");
			// View selectedView = mLoopViewGroup.getSelectedView();
			// if (selectedView != null) {
			// Log.d(TAG, "remove view : " + selectedView.getTag().toString());
			// mLoopViewGroup.removeView(selectedView);
			// }
			break;

		default:
			break;
		}

	}

}

代碼鏈接


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章