GridView(使用在TV或者盒子上)

本文介绍如何在TV或盒子应用中创建一个GridView,实现单元格固定行列、焦点动画以及滑动时的动画效果。在快速滑动时遇到了焦点偏差问题,通过BaseGridView和BaseGridViewContainer两个关键类进行解决,BaseGridViewContainer负责处理滑动和控制GridView大小,而BaseGridView则关注焦点选择监听的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需要的效果为:屏幕上固定几行几列的单元格,当焦点移动到某一个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;
	}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值