最近研究android的最常用的控件ListView,感觉挺有趣的,包括Listview的循环机制,item重用机制,上下滑动刷新机制,然后做了一个焦点可滑动的ListView,不知道大家看过或者用过Apple TV没有,Apple TV里面的焦点都是可滑动的, 大家可以搜索一下Apple TV 的视频, 可以看到焦点滑动的效果。
OK~! 先上图,不知道怎么贴视频或者是可以动的组件,只有贴几张图片了,会的同学请教教我,谢谢~!
这个是用键盘或者遥控按上下键的时候用的哦 对于手机来说意义不大 对于有遥控的android机顶盒 有键盘的PAD来说 还是有用的
这张是焦点的背景图片
这是焦点滑动的图片
这是item.xml的代码,放在layout目录下:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:gravity="center_vertical" android:paddingLeft="6dip" android:minHeight="?android:attr/listPreferredItemHeight" android:layout_weight="1" android:textColor="@android:color/white" />
ListView的代码是:
package com.test.ui; import java.lang.reflect.Method; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.Scroller; public class FocusScrollListView extends ListView { //记录当前焦点所在区间 private final byte FOCUS_MIDDLE = 0; private final byte FOCUS_BOTTOM = 1; private final byte FOCUS_TOP = 2; //ListView中每个item的高宽 private int itemWidth; private int itemHeight; //整个ListView的高度 private int listHeight; //焦点所在item的最上面距离parent容器最上面的距离差 private int top; //焦点所在位置 private byte mFocusState = FOCUS_MIDDLE; //背景图片,也可以不用任何图片而直接用一个颜色画一个和item等高等宽的矩形 private Bitmap mBitmap; //是否被调用了setSelection,如果调用了就必须强制刷新焦点图片的位置 private boolean isSetSelection; //Scroller类当前返回的数字,本项目下焦点的Y坐标 private int cordinatesY; //是否是屏幕滑动了,用于按键翻页的,强制刷新焦点图片位置 private boolean isPageScroll; //是否已经拿到了item的高度 private boolean hadHeight; //用于滑动的封装了加速减速器的计数类 private Scroller mScroller; private Matrix m; //scale X和Y,用于将任意图片拉伸到刚好填充item的空间 private float sy; private float sx; //焦点滑动的时间 private int sDuration = 5000; //翻页的API private Method method_pageScroll; //item批量上下刷新的API private Method method_arrowScrollImpl; //焦点是否正在滑动的过程中,焦点从一个item滑向相邻的另外一个item且滑动过程结束时该变量即为false private boolean isScroll; //记录离开当前ListView时所在的焦点位置,用于从ListView切换到另外的控件上然后再切换回来还能保持上次焦点所在的位置 private int tmpSelection; public FocusScrollListView(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); //禁用ListView上面的渐进边缘,也可以不禁用 setVerticalFadingEdgeEnabled(false); m = new Matrix(); //读取焦点背景图片 mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.channel_item_light); //通过反射初始化私有方法 initPrivateMethods(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); itemWidth = getWidth(); listHeight = getHeight(); //ListView刚创建的时候没有child,因为还没有setAdapter,但是创建的时候会调用layout,所以要判断一下,要不然会报错 if (getChildCount() > 0) { //如果获取ListView的高度了就不要再调用这个方法了,要不然某些情况高度会变成0,同时对性能也有好处 if (!hadHeight) { itemHeight = getChildAt(0).getHeight(); hadHeight = true; } sx = (float) itemWidth / mBitmap.getWidth(); sy = (float) itemHeight / mBitmap.getHeight(); m.setScale(sx, sy); //根据item宽高调整好拉伸背景图片 mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), m, true); } } //所有的效果都是通过这个回调方法完成,这个方法很重要 @Override protected void onDraw(Canvas canvas) { //如果外面调用了ListView的setSelection方法就会刷新并且返回,不执行下面的代码 if (isSetSelection) { if (null != getSelectedView()) { canvas.drawBitmap(mBitmap, 0, getSelectedView().getTop(), null); setScroller(getSelectedView().getTop()); isSetSelection = false; return; } } //焦点一边滑动一边刷新,知道Scroller滑动结束,将isScroll置false if (mScroller.computeScrollOffset()) { //不断的回调onDraw invalidate(); } else { if (isScroll) { isScroll = false; } } //如果是翻页就刷新 if (isPageScroll) { if (null != getSelectedView()) { cordinatesY = getSelectedView().getTop(); setScroller(getSelectedView().getTop()); isPageScroll = false; } } else { cordinatesY = mScroller.getCurrY(); } //上面一切都是为这个做准备 canvas.drawBitmap(mBitmap, 0, cordinatesY, null); } @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); //离开ListView时候记录焦点位置 if (getChildCount() > 0) { if (!gainFocus) { tmpSelection = getSelectedItemPosition(); } else { if (null != getSelectedView()) { setSelectionFromTop(tmpSelection, getSelectedView() .getTop()); } } } } @Override public void setSelection(int position) { super.setSelection(position); updateFocus(); } /** * The method of setMSelection() instead of setSelection(), so please call * setMSelection to set position of item * * @param position */ public void setMSelection(int position) { setTmpSelection(position); setSelection(position); isPageScroll = true; } private void updateFocus() { isSetSelection = true; } private void setTmpSelection(int position) { tmpSelection = position; } /** * return the number of items at present * * @return the number of items at present */ public int getItemNum() { return getChildCount(); } private void setScroller(int newY) { mScroller.setFinalY(newY); } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); setMSelection(0); } //ListView的item数量实际上是动态改变的,会在一个数值x和x+1甚至x+2之间徘徊,所以利用item的数量来计算焦点的移动是不行的,所以增加的实现此功能的复杂度 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //获得当前选中的item View view = getSelectedView(); //下面的就是逻辑上的东西了,在最上,最下,和中间进行不同的移动 if (null != view) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_DOWN: if (getLastVisiblePosition() == getAdapter().getCount() - 1 && getSelectedItemPosition() == getLastVisiblePosition() - 1 && mFocusState == FOCUS_MIDDLE) { top = view.getTop() + itemHeight + getDividerHeight(); mScroller.startScroll(0, view.getTop(), 0, top - view.getTop(), sDuration); isScroll = true; mFocusState = FOCUS_MIDDLE; break; } if (getSelectedItemPosition() < getLastVisiblePosition() - 1) { top = view.getTop() + itemHeight + getDividerHeight(); mScroller.startScroll(0, view.getTop(), 0, top - view.getTop(), sDuration); isScroll = true; mFocusState = FOCUS_MIDDLE; } else if (getSelectedItemPosition() == getLastVisiblePosition() - 1) { if (mFocusState != FOCUS_BOTTOM) { top = listHeight - itemHeight - getVerticalFadingEdgeLength() - getDividerHeight(); mScroller.startScroll(0, view.getTop(), 0, top - view.getTop(), sDuration); mFocusState = FOCUS_BOTTOM; } } break; case KeyEvent.KEYCODE_DPAD_UP: if (getSelectedItemPosition() == getFirstVisiblePosition() + 1) { if (mFocusState != FOCUS_TOP) { top = 0 + getDividerHeight() + getVerticalFadingEdgeLength(); mScroller.startScroll(0, view.getTop(), 0, top - view.getTop(), sDuration); mFocusState = FOCUS_TOP; } break; } if (getSelectedItemPosition() > getFirstVisiblePosition()) { top = view.getTop() - itemHeight - getDividerHeight(); mScroller.startScroll(0, view.getTop(), 0, top - view.getTop(), sDuration); mFocusState = FOCUS_MIDDLE; } break; } } Log.i("ListView", String.valueOf("listHeight " + listHeight + " itemHeight " + itemHeight + " top " + top)); return super.onKeyDown(keyCode, event); } //初始化私有方法 private void initPrivateMethods() { try { method_pageScroll = ListView.class.getDeclaredMethod("pageScroll", int.class); method_arrowScrollImpl = ListView.class.getDeclaredMethod( "arrowScrollImpl", int.class); method_pageScroll.setAccessible(true); method_arrowScrollImpl.setAccessible(true); } catch (Exception e) { e.printStackTrace(); } } //通过此方法设置焦点背景图片 public void setFocusBitmap(int resourceId) { mBitmap = BitmapFactory.decodeResource(getResources(), resourceId); } }
最后附上整个完整的工程源码::http://download.csdn.net/download/zhbinary/4100251