需要的效果为:屏幕上固定几行几列的单元格,当焦点移动到某一个item时有一个焦点框的动画效果,焦点针对是item中的imageView的大小,向上或者向下滑动时也需要动画效果。
遇到的问题是,当快速移动时,容易出现偏差。
代码结构
最主要的是三个类:
1.BaseGridView
package com.hm.gridviewtest;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
public class BaseGridView extends GridView {
OnItemSelectedListener mSelectedListener;
BaseGridView(Context context) {
this(context, null);
}
BaseGridView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
BaseGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setSelected(true);
setFocusable(true);
setClickable(true);
}
@Override
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
mSelectedListener = listener;
}
OnItemSelectedListener getOnBaseItemSelectedListener() {
return mSelectedListener;
}
void setOnBaseItemSelectedListener(OnItemSelectedListener listener) {
super.setOnItemSelectedListener(listener);
}
}
这个类很简单,而且是同包访问权限,最主要的方法是setOnItemSelectedListener,getOnBaseItemSelectedListener,setOnBaseItemSelectedListener,当外部调用的setOnItemSelectedListener时其实没有真正的设置监听,监听主要供BaseGridViewContainer来处理滑动
2.BaseGridViewContainer
这个类是最关键的,处理滑动,控制BaseGridView的大小等等。
package com.hm.gridviewtest;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.RelativeLayout;
import android.widget.AdapterView.OnItemSelectedListener;
public class BaseGridViewContainer extends RelativeLayout {
private static final String TAG = "BaseGridViewContainer";
protected View mBorderView;
protected BaseGridView mGridView;
// 显示行数
protected int mColumnCount = 0;
// 显示列数
protected int mRowCount = 0;
// item宽度和高度,固定
protected int mItemWidth;
protected int mItemHeight;
// 是否在滑动
private boolean mIsScrolling;
public BaseGridViewContainer(Context context) {
this(context, null);
}
public BaseGridViewContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseGridViewContainer(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BaseGridViewContainer, defStyle, 0);
mColumnCount = a.getInt(
R.styleable.BaseGridViewContainer_gridNumColumn, mColumnCount);
mRowCount = a.getInt(R.styleable.BaseGridViewContainer_gridNumRow,
mRowCount);
mItemWidth = a.getDimensionPixelSize(
R.styleable.BaseGridViewContainer_gridItemWidth, 0);
mItemHeight = a.getDimensionPixelSize(
R.styleable.BaseGridViewContainer_gridItemHeight, 0);
a.recycle();
mGridView = new BaseGridView(context);
mGridView.setNumColumns(mColumnCount);
mGridView.setSelector(new ColorDrawable(Color.TRANSPARENT));
mGridView.setOnBaseItemSelectedListener(mSelectedListener);
mGridView.setSelection(0);
mGridView.setOnScrollListener(mScrollListener);
LayoutParams lp = new LayoutParams(mColumnCount * mItemWidth, mRowCount
* mItemHeight);
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
addView(mGridView, lp);
mBorderView = new View(context);
mBorderView.setBackgroundResource(R.drawable.item_border);
addView(mBorderView, 0, 0);
}
public GridView getGridView() {
return mGridView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// 此处是真正的监听处理
OnItemSelectedListener mSelectedListener = new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View v, int position,
long id) {
if (v != null) {
// 选中时先初始滑动到0,0
scrollTo(0, 0);
// 计算滑动的偏差
int xOffset = v.getTop() - mItemHeight;
// 开始滑动动画
mGridView.smoothScrollBy(xOffset, 200);
}
// 焦点框滑动动画方法
animateBorder(v != null);
// 留给调用者的监听
OnItemSelectedListener l = mGridView
.getOnBaseItemSelectedListener();
if (l != null) {
l.onItemSelected(parent, v, position, id);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
OnItemSelectedListener l = mGridView
.getOnBaseItemSelectedListener();
mBorderView.setVisibility(GONE);
if (l != null) {
l.onNothingSelected(parent);
}
}
};
// 滑动监听,判断是否滑动,以及滑动停止时开始焦点框动画
OnScrollListener mScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
mIsScrolling = true;
break;
// 滑动停止
case OnScrollListener.SCROLL_STATE_IDLE:
mIsScrolling = false;
animateBorder(true);
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
mIsScrolling = false;
break;
default:
mIsScrolling = false;
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
};
// 处理焦点框的动画
private void animateBorder(boolean animated) {
if (mIsScrolling) {
return;
}
Log.i(TAG, "animateBorder animated = " + animated);
View v = mGridView.getSelectedView();
mBorderView.setVisibility(VISIBLE);
if (v instanceof BaseGridItemView) {
BaseGridItemView itemView = (BaseGridItemView) v;
Rect rect = itemView.getBorderRect();
int paddingLeft = mBorderView.getPaddingLeft();
int paddingRight = mBorderView.getPaddingRight();
int paddingTop = mBorderView.getPaddingTop();
int paddingBottom = mBorderView.getPaddingBottom();
int newWidth = paddingLeft + paddingRight + rect.width();
int newHeight = paddingTop + paddingBottom + rect.height();
// int x = rect.left + mGridView.getLeft() -
// mBorderView.getPaddingLeft() + mGridView.getScrollX();
// int y = rect.top + mGridView.getTop() -
// mBorderView.getPaddingTop() + mGridView.getScrollY();
int x = rect.left + mGridView.getLeft()
- mBorderView.getPaddingLeft();
int y = rect.top + mGridView.getTop() - mBorderView.getPaddingTop();
LayoutParams lp = (LayoutParams) mBorderView.getLayoutParams();
if (lp.width != newWidth || lp.height != newHeight) {
lp.width = newWidth;
lp.height = newHeight;
mBorderView.requestLayout();
}
if (animated) {
mBorderView.animate().x(x).y(y).setDuration(80).start();
} else {
mBorderView.setX(x);
mBorderView.setY(y);
}
}
if (v == null) {
postDelayed(new Runnable() {
@Override
public void run() {
animateBorder(false);
}
}, 50);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
// distance = 0时暂停滑动
// 此处很关键,当快速滑动时,容易出现位移偏差,当按键时将上次没有滑动完成停止,并滑动对应的地方
mGridView.smoothScrollBy(0, 0);
}
return super.dispatchKeyEvent(event);
}
}
3.BaseGridItemView
这个类是每一个grid子项,它有一个基类方法,计算当前焦点框的区域。
package com.hm.gridviewtest;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
public abstract class BaseGridItemView extends BaseItemView {
public BaseGridItemView(Context context) {
this(context, null);
}
public BaseGridItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseGridItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public abstract Rect getBorderRect();
}
例如GriditemView计算item中iamgeView的所在区域。
@Override
public Rect getBorderRect() {
Rect rect = new Rect();
if (mIcon != null) {
int width = mIcon.getWidth();
int height = mIcon.getHeight();
View parent = mIcon;
do {
rect.left += parent.getLeft();
rect.top += parent.getTop();
rect.right = rect.left + width;
rect.bottom = rect.top + height;
parent = (View) parent.getParent();
} while (parent != this);
rect.left += getLeft();
rect.top += getTop();
rect.right = rect.left + width;
rect.bottom = rect.top + height;
}
return rect;
}