效果:固定的子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;
}
}
}
代碼鏈接