不断学习,做更好的自己!💪
视频号CSDN简书欢迎打开微信,关注我的视频号:KevinDev点我点我LeanBack 是 Google 官方推出的 TV 端的功能库,里面包含了很多在 TV Android 端开发常用的控件,本文重点介绍其对 RecyclerView 适配 TV 端做的封装:VerticalGridView 。
效果图-
focusOutFront、focusOutEnd 如果标题栏使用 HorizontalGridView 实现,内容区域使用 Fragment 里放的 VerticalGridView 实现,可能出现标题栏和内容区焦点切换不成功的问题,比如说,焦点不能从内容区切到标题栏这样的情况。这时使用 focusOutFront 和 focusOutEnd 属性能够解决问题,解决不同容器里焦点切换不成功的问题。
-
setHorizontalSpacing(),setVerticalSpacing() 设置 VerticalGridView 的 Item 之间的间距。
-
setNumColumns() 设置列数,默认 VerticalGridView 为一列,通过 setNumColumns 方法可以设置多列。但有个注意点,设置多行后要注意 position 的位置。
1. 添加依赖
implementation 'androidx.leanback:leanback:1.0.0'
implementation 'androidx.leanback:leanback-preference:1.0.0'
2. 布局文件
3. AppVerticalGridView.java
public class AppVerticalGridView extends VerticalGridView {
private static final String TAG = "AppVerticalGridView";
private int mNumColumns;
public AppVerticalGridView(Context context) {
this(context, null);
}
public AppVerticalGridView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AppVerticalGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setColumnNumbers(int numColumns) {
this.mNumColumns = numColumns;
setNumColumns(numColumns);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BACK:
if (getSelectedPosition() > 0) {
setSelectedPosition(0);
return true;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (mNumColumns == 0 || getAdapter() == null) {
break;
}
int itemCount = getAdapter().getItemCount();
if (itemCount + 1 itemCount)) {
setSelectedPositionSmooth(itemCount - 1);
return true;
}
break;
}
}
return super.dispatchKeyEvent(event);
}
}
4. Bean 类
public class HomeAppModel {
public String dataDir;
public Drawable icon;
public String id;
public String name;
public String launcherName;
public String packageName;
public int pageIndex;
public int position;
public boolean sysApp;
public String getDataDir() {
return this.dataDir;
}
public Drawable getIcon() {
return this.icon;
}
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public String getPackageName() {
return this.packageName;
}
public int getPageIndex() {
return this.pageIndex;
}
public int getPosition() {
return this.position;
}
public void setDataDir(String paramString) {
this.dataDir = paramString;
}
public void setIcon(Drawable paramDrawable) {
this.icon = paramDrawable;
}
public void setId(String paramString) {
this.id = paramString;
}
public void setName(String paramString) {
this.name = paramString;
}
public void setPackageName(String paramString) {
this.packageName = paramString;
}
public void setPageIndex(int paramInt) {
this.pageIndex = paramInt;
}
public void setPosition(int paramInt) {
this.position = paramInt;
}
public String toString() {
return "AppBean [packageName=" + this.packageName + ", name=" + this.name + ", dataDir=" + this.dataDir + "]";
}
public boolean isSysApp() {
return sysApp;
}
public void setSysApp(boolean sysApp) {
this.sysApp = sysApp;
}
public String getLauncherName() {
return launcherName;
}
public void setLauncherName(String launcherName) {
this.launcherName = launcherName;
}
}
5. 创建 Presenter
public class HomePresenter extends Presenter {
private Context mContext;
@Override
public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
if (mContext == null) {
mContext = parent.getContext();
}
View view = LayoutInflater.from(mContext).inflate(R.layout.item_home_app, parent, false);
view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
v.findViewById(R.id.tv_app_name).setSelected(hasFocus);
}
});
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
if (item instanceof HomeAppModel) {
ViewHolder vh = (ViewHolder) viewHolder;
if (((HomeAppModel) item).icon != null) {
Bitmap bitmap = getBitmapFromDrawable(((HomeAppModel) item).icon);//适配Android 8.0
RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), bitmap);
drawable.setCornerRadius(DensityUtil.dip2px(mContext, 10));
vh.mIvAppIcon.setImageDrawable(drawable);
}
if (!TextUtils.isEmpty(((HomeAppModel) item).name)) {
vh.mTvAppName.setText(((HomeAppModel) item).name);
}
}
}
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
}
public static class ViewHolder extends Presenter.ViewHolder {
private final ImageView mIvAppIcon;
private final TextView mTvAppName;
public ViewHolder(View view) {
super(view);
mIvAppIcon = view.findViewById(R.id.iv_app_icon);
mTvAppName = view.findViewById(R.id.tv_app_name);
}
}
private Bitmap getBitmapFromDrawable(@NonNull Drawable drawable) {
final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bmp);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bmp;
}
}
6. MyItemBridgeAdapter.java
public abstract class MyItemBridgeAdapter extends ItemBridgeAdapter {
protected MyItemBridgeAdapter(ObjectAdapter adapter) {
super(adapter, null);
}
@Override
protected void onBind(final ViewHolder viewHolder) {
if (getOnItemViewClickedListener() != null) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getOnItemViewClickedListener().onItemClicked(v, viewHolder.getViewHolder(),
viewHolder.getItem());
}
});
viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (getOnItemViewLongClickedListener() != null) {
return getOnItemViewLongClickedListener().onItemLongClicked(v, viewHolder.getViewHolder(),
viewHolder.getItem());
}
return true;
}
});
}
if (getOnItemFocusChangedListener() != null) {
viewHolder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
getOnItemFocusChangedListener().onItemFocusChanged(v, viewHolder.getViewHolder(),
viewHolder.getItem(), hasFocus, viewHolder.getAdapterPosition());
}
});
}
super.onBind(viewHolder);
}
@Override
protected void onUnbind(ViewHolder viewHolder) {
super.onUnbind(viewHolder);
viewHolder.itemView.setOnClickListener(null);
if (getOnItemFocusChangedListener() != null) {
viewHolder.itemView.setOnFocusChangeListener(null);
}
}
public abstract OnItemViewClickedListener getOnItemViewClickedListener();
public OnItemViewLongClickedListener getOnItemViewLongClickedListener() {
return null;
}
public OnItemFocusChangedListener getOnItemFocusChangedListener() {
return null;
}
public interface OnItemViewClickedListener {
void onItemClicked(View focusView, Presenter.ViewHolder itemViewHolder, Object item);
}
public interface OnItemViewLongClickedListener {
boolean onItemLongClicked(View focusView, Presenter.ViewHolder itemViewHolder, Object item);
}
public interface OnItemFocusChangedListener {
void onItemFocusChanged(View focusView, Presenter.ViewHolder itemViewHolder, Object item, boolean hasFocus, int pos);
}
}
7. 使用
public class HomeFragment extends BaseFragment {
@BindView(R.id.vg_app_installed)
AppVerticalGridView mGridView;
private ArrayObjectAdapter mAdapter;
@Override
protected int layoutId() {
return R.layout.fragment_home;
}
@SuppressLint("RestrictedApi")
@Override
protected void initView(View view) {
mGridView.setNumColumns(6);
mGridView.setHorizontalSpacing(DensityUtil.dip2px(getActivity(), 53));
mGridView.setVerticalSpacing(DensityUtil.dip2px(getActivity(), 20));
mAdapter = new ArrayObjectAdapter(new HomePresenter());
ItemBridgeAdapter itemBridgeAdapter = new MyItemBridgeAdapter(mAdapter) {
@Override
public OnItemViewClickedListener getOnItemViewClickedListener() {
return new OnItemViewClickedListener() {
@Override
public void onItemClicked(View focusView,
Presenter.ViewHolder itemViewHolder,
Object item) {
if (focusView.hasFocus()
&& item instanceof HomeAppModel) {
try {
PackageManager packageManager = getActivity().getPackageManager();
Intent intent = packageManager
.getLaunchIntentForPackage(((HomeAppModel) item).packageName);
if (intent == null) {
Toast.makeText(getActivity(),
((HomeAppModel) item).name + "未安装",
Toast.LENGTH_SHORT).show();
} else {
startActivity(intent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
}
@Override
public OnItemViewLongClickedListener getOnItemViewLongClickedListener() {
return new OnItemViewLongClickedListener() {
@Override
public boolean onItemLongClicked(View focusView,
Presenter.ViewHolder itemViewHolder,
Object item) {
if (focusView.hasFocus()
&& item instanceof HomeAppModel
&& !TextUtils.isEmpty(((HomeAppModel) item).packageName)) {
Uri packageURI = Uri.parse("package:" + ((HomeAppModel) item).packageName);
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
uninstallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(uninstallIntent);
}
return true;
}
};
}
};
mGridView.setAdapter(itemBridgeAdapter);
FocusHighlightHelper.setupBrowseItemFocusHighlight(itemBridgeAdapter,
FocusHighlight.ZOOM_FACTOR_MEDIUM, false);
initData();
}
private void initData() {
List appInfos = getInstallApps(getContext());
if (appInfos == null) {
return;
}
mAdapter.addAll(0, appInfos);
}
public List getInstallApps(Context context) {
PackageManager pm = context.getPackageManager();
List installedPackages = pm.getInstalledPackages(0); //获取所以已安装的包
List list = new ArrayList();
for (PackageInfo packageInfo : installedPackages) {
Intent intent = pm.getLaunchIntentForPackage(packageInfo.packageName);
if (intent == null) {
continue;
}
HomeAppModel info = new HomeAppModel();
ApplicationInfo applicationInfo = packageInfo.applicationInfo; //应用信息
info.name = applicationInfo.loadLabel(pm).toString();
info.icon = applicationInfo.loadIcon(pm); //状态机,通过01状态来表示是否具备某些属性和功能
info.packageName = packageInfo.packageName;
list.add(info);
}
Log.e(TAG, "getInstallApps1: ");
return list;
}
}