首先,给出整个代码的效果图:
这是整个壁纸库应用的效果图,项目地址在这里
一、GridView的介绍:
Gridview是一个ViewGroup,它展示的是一个二维的,可滑动的表格.这个表格的item是自动插入的布局,默认使用一个ListAdapter容器。
1.GridView填充数据,这个东西网上的内容太多了,这里不再赘述,只是有一个点需要注意一下:
gridview在笔者实现放大和缩小动画效果的过程中,为了保持选中的item属于最后执行的效果,重写了一下Gridview的getChildDrawingOrder()方法;同时为了避免Gridview在测量item的时候过渡绘制,导致界面卡顿,重写了onMeasure()方法,这里对此作出解释。
代码如下:
/**
* Created by fengjw on 2018/3/12.
*/
public class MyGridView extends GridView {
public boolean isOnMeasure;
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
setChildrenDrawingOrderEnabled(true); //这里设置为true才能调用getChildDrawingOrder()方法
}
@Override
public void setSelection(int position) {
Constants.debug("MyGridView setSelection position : " + position);
Constants.position = position;
super.setSelection(position);
}
@Override
protected void setChildrenDrawingOrderEnabled(boolean enabled) {
super.setChildrenDrawingOrderEnabled(enabled);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
isOnMeasure = true;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
isOnMeasure = false;
super.onLayout(changed, l, t, r, b);
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
if (this.getSelectedItemPosition() != -1){ //不选中默认为-1(INVALID_POSITION),一般从0开始
if (i + this.getFirstVisiblePosition() == this.getSelectedItemPosition()){//getFirstVisiblePosition获得的是第一个item的位置
return childCount - 1;
}
if (i == childCount - 1){
return this.getSelectedItemPosition() - this.getFirstVisiblePosition();
}
}
return i;
}
}
【关于避免过度测绘的问题,
请看这里】
一般来说,GridView的绘制顺序是自上而下的,这样会导致后面的item覆盖了前面的item,所以我们就重写getChildDrawingOrder()方法来改变绘制的顺序。
实现的逻辑是这样的:当我们选中某个item的时候,当前item 也就是i+this.getFirstVisiblePosition的值如果等于选中的position,那么就return childCount -1;也就是最后一个item;不然的话就返回当前的item。这样就会让最后一个绘制的item是我们选择的item。
2.GridView的常用方法介绍:
常用参数:
columnWidth:设置每列固定宽度
gravity:设置每个item的中心
horizontalSpacing:定义水平行间距
numColumns:设置每列显示的数目
stretchMode:定义如何使用多余的空空间.
verticalSpacing:定义垂直行间距
get常用方法:
getNumColumns:得到grid每列的数目
set常用方法:
setNumColumns:设置每列显示的数目
setOnKeyListener:gridview作为一个view的按键监听
setOnItemClickListener:gridview内部item的点击监听
setOnItemSelectedListener:gridview内部选中的监听
setAdapter:设置adapter
setFocusable:设置gridview是否获得焦点
另一个是gridview的刷新:
gridview的刷新主要通过两种方式:
a.setAdapter来刷新容器内容
b.使用容器依赖的Adapter的notifyDataSetChanged().
二、GridView的使用:
1.首先设置一个GridView在资源文件中,并使用自定义MyGridView来extends GridView,重写一些方法。
scrollbars:设置为none,不让侧边的滚动条出现。
listSelector设置的目的是为了消除选中的item的背景黄色。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_selected="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#00000000"/>
</shape>
</item>
<item android:state_pressed="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#00000000"/>
</shape>
</item>
<item android:state_focused="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#00000000"/>
</shape>
</item>
</selector>
2.在onCreate中设置Gridiveiw的属性,因为是TV开发,我们这里主要注意焦点的位置及处理。
private void initGridView() {
mGridAdapter = new GridAdapter(this, mList, gridview);
gridview.setAdapter(mGridAdapter);
gridview.setOnKeyListener(new GridViewOnKeyListener());
gridview.setOnItemClickListener(new GridViewOnItemClickListener());
gridview.setOnItemSelectedListener(new GridViewOnItemSelectedListener());
}
如最开始的UI效果图,我们在离开gridview和进入gridview的时候,要手动进行focus的设置。为了记录原来离开时Focus的位置,我们定义一个全局的变量,如图:
在onItemSelectedListener中,我们每次进行一次改变当前item的操作,都记录一下position:
private class GridViewOnItemSelectedListener implements AdapterView.OnItemSelectedListener{
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Constants.debug("onItemSelected position : " + position);
Constants.position = position;
currentSelectPosition = position;
mGridAdapter.setSelection(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
Constants.debug("onNothingSelected");
mGridAdapter.setSelection(-2);
}
}
而每次按键的时候,都会响应onKey()操作,我们在GridView的onkey监听中做一下position的处理:
private class GridViewOnKeyListener implements View.OnKeyListener{
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Constants.debug("GridViewOnKeyListener onKey()");
if (event.getAction() == KeyEvent.ACTION_DOWN){
if (keyCode == KeyEvent.KEYCODE_DPAD_UP ){//这里是按键向上的操作
if (currentSelectPosition == 3){ //当前选中的position
mGridAdapter.setSelection(-2);
mMainTopMarketRlRoot.setFocusable(false);
mMainTopHomeRlRoot.setFocusable(false);
mMainArrowUpRlRoot.setVisibility(View.INVISIBLE);
mMainArrowDownRlRoot.setVisibility(View.INVISIBLE);
wifiFg.getView().setFocusable(true);
}
if (currentSelectPosition >= 0 && currentSelectPosition <= 2){
Constants.debug(".........");
// Constants.position = -2;
// currentSelectPosition = -2;
mGridAdapter.setSelection(-2);
mMainTopMarketRlRoot.setFocusable(true);
mMainTopHomeRlRoot.setFocusable(false);
mMainArrowUpRlRoot.setVisibility(View.INVISIBLE);
mMainArrowDownRlRoot.setVisibility(View.INVISIBLE);
}else if (currentSelectPosition > 11){
mMainArrowUpRlRoot.setVisibility(View.VISIBLE);
mMainArrowDownRlRoot.setVisibility(View.INVISIBLE);
}else if (currentSelectPosition >= 8 && currentSelectPosition <=11){
mMainArrowUpRlRoot.setVisibility(View.VISIBLE);
mMainArrowDownRlRoot.setVisibility(View.VISIBLE);
}else if (currentSelectPosition >= 4 && currentSelectPosition <=7){
mMainArrowUpRlRoot.setVisibility(View.INVISIBLE);
mMainArrowDownRlRoot.setVisibility(View.VISIBLE);
}
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
if (currentSelectPosition >= 8 && currentSelectPosition <= 11){
mMainArrowUpRlRoot.setVisibility(View.VISIBLE);
mMainArrowDownRlRoot.setVisibility(View.INVISIBLE);
}else if (currentSelectPosition >= 4 && currentSelectPosition <= 7){
mMainArrowUpRlRoot.setVisibility(View.VISIBLE);
mMainArrowDownRlRoot.setVisibility(View.VISIBLE);
}
}
}
return false;
}
}
同时,我们要更新GridView内容,通过在GridAdapter中设置一个方法体来实现:
public void setSelection(int position){
selectItem = position;
Constants.debug("selectItem : " + selectItem);
settingPosition = mPreference.loadSharedPreferences("settingPosition", -2);
Constants.debug("----------------------");
Constants.debug("settingPosition : " + settingPosition);
Constants.debug("----------------------");
super.notifyDataSetChanged();//刷新adapter
}
刷新adapter会调用getView方法,从而实现数据的改变操作。
同理,当我们在进入GridView的时候,设置GridView的setFocusable为true,并调用GridAdapter的setSelection方法来达到更新效果。
三、fragment焦点改变的问题
UI效果图第一张的右上角的图标在获得焦点后无法通过监听key值来更改Focus,发现该控件为fragment有很大关系。
所以,我选择了一种方法,通过监听window层的onKeyDown事件来处理,并通过获得当前Focus的View Id方式来手动更改焦点的位置,代码如下:
public boolean onKeyDown(int keyCode, KeyEvent event) {
Constants.debug("onKeyDown");
View rootview = this.getWindow().getDecorView();
int focusId = rootview.findFocus().getId();//这里是获得当前focus id的方法
Constants.debug("focusId : " + focusId);
if (focusId == R.id.root_main_top_wifi_fg){
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT){
wifiFg.getView().setFocusable(false);
mMainTopHomeRlRoot.setFocusable(false);
mMainTopMarketRlRoot.setFocusable(true);
gridview.setFocusable(false);
}
if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DPAD_DOWN){
wifiFg.getView().setFocusable(false);
mMainTopHomeRlRoot.setFocusable(false);
mMainTopMarketRlRoot.setFocusable(false);
gridview.setFocusable(true);
gridview.requestFocus();
currentSelectPosition = Constants.position;
mGridAdapter.setSelection(Constants.position);
mMainArrowUpRlRoot.setVisibility(View.INVISIBLE);
mMainArrowDownRlRoot.setVisibility(View.VISIBLE);
}
}
return super.onKeyDown(keyCode, event);//不阻断按键时间的传递
}
总结:GridView不难,但是要快速上手,还是需要理解一些基础性的东西才行。
========================================
补充:2018.06.08
解决了onCreate gridview的时候,第一个item选中无法放大的方法:
执行onItemSelection的时候,选中第一个item默认会执行放大的动画,但是在第一次启动有Gridview的界面并默认让第一个item持有focus的时候,大多数情况下并不会有该动画,这其实是GridView设计上的一个缺陷。解决方法很简单:
我们调用Gridiview支持的方法会发现,每次当GridView第一次获得焦点的时候,都首次会执行一个onFocusChange的方法,那么我们在这里从新执行setSelection(0),并通过notifyDataSetChanged刷新adapter,来达到我们的目的。
如图: