应该做过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