android GridView 在TV上,上下翻页的时候平滑滑动的实现

时间:2021-06-11 14:47:41

应该做过android tv开发的同学都知道,在TV上使用GridView的时候,如果焦点上下移动的时候,如果移动到在屏幕上可见的第一行或者最后一行的时候,如果再继续上下移动,

的话,是比较生硬呆滞的的上下滚动页面,焦点移动到下一个item上,这是非常不太好的体验效果,我们要的是比较平滑的滚动效果。


首先我们要来了解一下GridView的如下这个方法:

smoothScrollToPositionFromTop(int position,int offset,int duration);

滚动到position项目的位置,并且position项目距离GirdView上边的距离为offset个像素,duration指定滚动需要的时间(毫秒)。

这个方法是今天的主角,虽然是主角,但不是说只有使用这个方法就可以了?当然不是,当然也需要一定的逻辑算法配合。



今天就说说我是如何实现的。

先说说我的思路:

首先你要知道当前item所在的行,才知道按上下按键的时候,要滚动到哪一个postion。

public class AppsGridViewAdapter extends BaseAdapter {
	private int mCurrentSelectedPosition = 0;
	private int mLastSelectedPosition = 0;
	private static final int ITEM_BG_COLORS[] = new int[]{
		Color.parseColor("#e0c101"),
		Color.parseColor("#fbaf54"),
		Color.parseColor("#684cb5"),
		Color.parseColor("#23a5ba"),
		Color.parseColor("#03ba9f"),
		Color.parseColor("#88c04c"),
		Color.parseColor("#684cb5"),
		Color.parseColor("#23a5ba"),
		Color.parseColor("#03ba9f"),
		Color.parseColor("#88c04c"),
		Color.parseColor("#e0c101"),
		Color.parseColor("#fbaf54"),
	};
	private int mColorIndex = 0;
	private List<AppInfo> mApps = new ArrayList<AppInfo>();
	private Context mContext;
	private int mGridViewRows = 0;
	public AppsGridViewAdapter(Context context, List<AppInfo> apps) {
		this.mContext = context;
		if(apps!=null)
			this.mApps = apps;
		setRows();
		mColorIndex = 0;
	}



	private void setRows() {
		int remainder = mApps.size() % 6;
		mGridViewRows =(remainder == 0)? (this.mApps.size() > 0 ?  this.mApps.size() / 6:0) : (mApps.size() / 6) +1;
	}
	
	
	
	public int getNumRows() {
		return mGridViewRows;
	}

	public int getNumColumns(){
		return 6;
	}


	@Override
	public void notifyDataSetChanged() {
		mColorIndex = 0;
		super.notifyDataSetChanged();
	}
	
	public void setApps(List<AppInfo> apps){
		mApps.clear();
		if(apps!=null)
			mApps = apps;
		setRows();
	}
	
	
	public void addApp(AppInfo app){
		if(app!=null && app.isValidate())
			mApps.add(app);
		setRows();
	}
	
	public void removeApp(AppInfo app){
		if(app!=null)
			mApps.remove(app);
		setRows();
	}
	
	
	
	public int getCurrentSelectedPosition() {
		return mCurrentSelectedPosition;
	}




	public int getLastSelectedPosition() {
		return mLastSelectedPosition;
	}




	@Override
	public int getCount() {
		return mApps.size();
	}

	@Override
	public Object getItem(int position) {
		return mApps.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder = null;
		if(convertView == null){
			 convertView = View.inflate(mContext, R.layout.apps_page_gridview_item_layout, null);
			 holder = new ViewHolder();
			 holder.mAppIcon = (ImageView) convertView.findViewById(R.id.app_icon);
			 holder.mAppName = (TextView) convertView.findViewById(R.id.app_name);
			 holder.mAppIconColorBg = (RelativeLayout) convertView.findViewById(R.id.app_icon_color_bg);
			 convertView.setTag(holder);
		}
		holder = (ViewHolder) convertView.getTag();
		holder.mAppName.setText(mApps.get(position).getName());
		holder.mAppIcon.setBackgroundDrawable(mApps.get(position).getIcon());
		holder.mAppIconColorBg.setBackgroundColor(ITEM_BG_COLORS[mColorIndex]);
		++mColorIndex;
		mColorIndex =( (mColorIndex / 6) != 0 && (mColorIndex / 6) % 2 == 0 ) ? 0 :mColorIndex;
		return convertView;
	}
	
	private static final class ViewHolder{
		private ImageView mAppIcon;
		private TextView mAppName;
		private RelativeLayout mAppIconColorBg;
	}


}

我们看到,Adapter中定一个了两个方法,可以用来获取多少行,和多少列(列一般是已知的)。

setRows() ;是每次设置给adapter数据的时候,都重新弄设置一下总行数。

getNumRows,返回获取当前所有的行数,getNumColumns返回所有的列数。


OK,接下来,我们就可以通过postion来算出当前选中的postion在哪一行了。

package net.sunniwell.projector.luncher.page;

import java.util.List;

import net.sunniwell.projector.luncher.CategoryPageActivity;
import net.sunniwell.projector.luncher.R;
import net.sunniwell.projector.luncher.adapter.AppsGridViewAdapter;
import net.sunniwell.projector.luncher.bean.AppInfo;
import net.sunniwell.projector.luncher.engine.PackageAsyncTask;
import net.sunniwell.projector.luncher.engine.PackageAsyncTask.OnQueryPackageStateListener;
import net.sunniwell.projector.luncher.engine.PackageEngine;
import net.sunniwell.projector.luncher.engine.PackageEngine.OnPackageStateChangeListener;
import net.sunniwell.projector.luncher.views.CommonGridView;
import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.TextView;
/**
 * 
 * @author zhanghuagang 2017.7.6
 *
 */
public class AppsPage extends BasePage implements OnPackageStateChangeListener, OnItemSelectedListener,OnQueryPackageStateListener ,OnItemClickListener{
	static{
		sCategory = 3;
	}
	private int mCurrentRow = 0;
	private TextView mIndexTextView;
	private CommonGridView mGridView;
	private AppsGridViewAdapter mAppsAdapter;
	private PackageAsyncTask mAsyncTask;
	private PackageEngine mPackageEngine;
	public AppsPage(CategoryPageActivity activity, ViewGroup contentView,int category) {
		super(activity, contentView, category);
		this.mPackageEngine = PackageEngine.getDefault(activity);
		this.mPackageEngine.setOnPackageStateChangeListener(this);
	}
	
	@Override
	public void initView() {
		mIndexTextView = (TextView) mContentView.findViewById(R.id.index_textview);
		mGridView = (CommonGridView) mContentView.findViewById(R.id.apps_gridview);
		mGridView.setOnItemSelectedListener(this);
		mGridView.setOnItemClickListener(this);
		mAppsAdapter = new AppsGridViewAdapter(mActivity,null);
		mGridView.setAdapter(mAppsAdapter);
		mAppsAdapter.notifyDataSetChanged();
	}

	@Override
	public void onResume() {
		mPackageEngine.register();
		queryApps();
	}

	private void queryApps() {
		if(mAsyncTask!=null)
			mAsyncTask.stop();
		mAsyncTask = new PackageAsyncTask(mActivity, this);
		mAsyncTask.start();
	}

	@Override
	public void onStop() {
		
	}

	@Override
	public void onPause() {
		mPackageEngine.unregister();
	}

	@Override
	public void onDestroy() {
	}
	
	private int getItemIndex(int position){
				int r = position % mAppsAdapter.getNumColumns() ;
				if(r  == 0){
					 if(position > 0 ){
						  return (position/mAppsAdapter.getNumColumns())+1;
					 }else{
						 return 1;
					 }
				}else{
				    return (position/mAppsAdapter.getNumColumns())+1;
				}
	}

	@Override
	public void onItemSelected(AdapterView<?> parent, View view, int position,
			long id) {
		if(getItemIndex(position) > mCurrentRow){
			mGridView.smoothScrollToPositionFromTop(position, 0,500);
		}else if(getItemIndex(position) < mCurrentRow){
			mGridView.smoothScrollToPositionFromTop(position-2, 0,500);
		}
		mGridView.onItemSelected(parent, view, position, id);
		mIndexTextView.setText(getItemIndex(position)+"  |  "+mAppsAdapter.getNumRows());
		mCurrentRow =getItemIndex(position); 
	}

	@Override
	public void onNothingSelected(AdapterView<?> parent) {
		
	}

	@Override
	public void onFinish(List<AppInfo> apps) {
			mAppsAdapter.setApps(apps);
			mAppsAdapter.notifyDataSetChanged();
	}

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position,long id) {
		AppInfo app = (AppInfo) mAppsAdapter.getItem(position);
		if(app!=null){
			final Intent intent = new Intent();
			intent.setClassName(app.packageName, app.className);
			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			mActivity.startActivity(intent);
		}
	}

	@Override
	public void onPackageAdded(AppInfo app) {
		mAppsAdapter.addApp(app);
		mAppsAdapter.notifyDataSetChanged();
	}

	@Override
	public void onPackageDeleted(AppInfo app) {
		mAppsAdapter.removeApp(app);
		mAppsAdapter.notifyDataSetChanged();
	}
}

我的getItemIndex(int postion)就是根据position获取当前所在行的。


然后我们在onItemSelected对平滑滑动做处理。

如果下一个获取焦点的item所在行小于当前行,说明是向上按的,如果是大于当前行,说明是向下按住的。

这里我们特殊的处理的向下按的,position-2,这个意思就是当我向下按的时候,每次移动两个postion的距离,并且postion和边缘距离为0