因为项目需要,花了一天做了一个自定义listview,和google官方效果图上的控件类似,效果图:

即焦点始终在屏幕中央,焦点选中的item被放大,且颜色被改变,遥控器可以指挥listview上下滚动并且选择,要做到这个首先要想清楚思路:

核心思想

1、中间的“焦点”不是真实的焦点,是画出来的。

2、移动listview使用的方法是listview.smoothScrollBy,但当选择到前几个item或后几个item时,会有留白的情况,如图,为了解决这个问题,我有想过整体移动listview,最后由于越搞越麻烦而放弃,在这里使用了控制adapter,使其产生空白选项,这样不用移动listview也能解决此问题。

3、对于移动状态进行监听,发现移动产生后改变item中字体的效果。

对于以上的核心思想,做几点解释:

1、我们使用listview.setSelection()方法使焦点移动到想要的item上,但是频繁调用这个方法会让焦点出现闪烁的情况,尤其是在自定义了焦点的样式后,这种情况比较明显。为了保证在移动时控件的美观,我们使用绘制矩阵的方式,将焦点画出来。

下面上代码,该有注释的地方都有:

package com.pu.test;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;import java.util.List;/*** 通用型焦点始终位于中心且带滚动动画的listview* * @author puky*/
public class WheelListView extends ListView {public static final String TAG = WheelListView.class.getSimpleName();private static final int COLOR_SOLID_SELET_DEFAULT = Color.parseColor("#E01F1F");// total of several item are displayed by defaultprivate static final int WHEEL_SIZE_DEFAULT = 3; //Callback delay timeprivate static final int MIN_TIME = 300; private static final int HANDLER_LISTENER = 99;private static final int LAST_HANDLER_LISTENER = 100;//The size of the data used for the calculationprivate int mDataSize = 0;private int mLabelSelectColor = Color.WHITE;private int mLabelColor = Color.GRAY;private float mAlphaGradual = 0.5f;private int seletedSolidColor = COLOR_SOLID_SELET_DEFAULT;private int mWheelSize = WHEEL_SIZE_DEFAULT;private int mItemHeight;private int mItemWidth;private int mCurrentPositon;private Paint seletedSolidPaint;private boolean isRecover = false;private int proSelection = 0;private int curSelection = 0;private WheelItemSelectedListener mItemSelectedListener;public WheelListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public WheelListView(Context context, AttributeSet attrs) {super(context, attrs);init();}public WheelListView(Context context) {super(context);}private void init() {setVerticalScrollBarEnabled(false); // Hide the scroll barsetCacheColorHint(Color.TRANSPARENT);setOverScrollMode(OVER_SCROLL_NEVER);setDividerHeight(0);seletedSolidPaint = new Paint();seletedSolidPaint.setColor(seletedSolidColor);setOnScrollListener(new OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {// Stop scrollingif (scrollState == SCROLL_STATE_IDLE) {View itemView = getChildAt(0);if (itemView != null) {/*** Fine tuning*/float deltaY = itemView.getY();if (deltaY == 0) {return;}Log.d(TAG, "WheelListView deltaY=" + deltaY + ""+ ", mItemHeight ==" + mItemHeight);if (Math.abs(deltaY) < mItemHeight / 2) {// not half, scroll backsmoothScrollBy(getDistance(deltaY), 10);} else {// more than half, scroll nextsmoothScrollBy(getDistance(mItemHeight + deltaY), 10);}}}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {refreshItems();}});setOnFocusChangeListener(new OnFocusChangeListener() {@Overridepublic void onFocusChange(View v, boolean hasFocus) {if (hasFocus) {invalidate();}}});}private int getDistance(float scrollDistance) {if (Math.abs(scrollDistance) <= 2) {return (int) scrollDistance;} else if (Math.abs(scrollDistance) < 12) {return scrollDistance > 0 ? 2 : -2;} else {return (int) (scrollDistance / 6);}}//used for callbackprivate void refreshItems() {int offset = mWheelSize / 2;int firstPosition = getFirstVisiblePosition();int position = 0;if (getChildAt(0) == null) {return;}if (Math.abs(getChildAt(0).getY()) <= mItemHeight / 2) {position = firstPosition + offset;} else {position = firstPosition + offset + 1;}if (position == mCurrentPositon) {return;}mCurrentPositon = position;if (mItemSelectedListener != null) {handler.sendEmptyMessageDelayed(HANDLER_LISTENER, MIN_TIME);}resetItems(firstPosition, position, offset);}private void resetItems(int firstPosition, int position, int offset) {for (int i = position - offset - 1; i < position + offset + 1; i++) {View itemView = getChildAt(i - firstPosition);if (itemView == null) {continue;}TextView labelTv = (TextView) itemView.findViewById(R.id.type_name);if (position == i) {if (isRecover && getSelection() == 0) {labelTv.setTextColor(mLabelColor);isRecover = false;} else {labelTv.setTextColor(mLabelSelectColor);}labelTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, 55);itemView.setAlpha(1f);} else {labelTv.setTextColor(mLabelColor);labelTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, 35);itemView.setAlpha(mAlphaGradual);}}}/*** @return true location*/private int getPosition() {int offset = mWheelSize / 2;if (getChildAt(0) == null) {return 0;}if (Math.abs(getChildAt(0).getY()) <= mItemHeight / 2) {return offset;} else {return offset + 1;}}@Overrideprotected void onMeasure(int arg0, int arg1) {super.onMeasure(arg0, arg1);mItemWidth = getWidth();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (this.isFocused()) {canvas.drawRect(0, mItemHeight * (mWheelSize / 2), mItemWidth,mItemHeight * (mWheelSize / 2 + 1), seletedSolidPaint);}}/*** 焦点变化时,中间选中框字体颜色变化*/@Overrideprotected void onFocusChanged(boolean arg0, int arg1, Rect arg2) {super.onFocusChanged(arg0, arg1, arg2);View itemView = getChildAt(getPosition());TextView labelTv = (TextView) itemView.findViewById(R.id.type_name);if (this.isFocused()) {labelTv.setTextColor(mLabelSelectColor);} else {labelTv.setTextColor(mLabelColor);}}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {int keyCode = event.getKeyCode();if (event.getAction() == KeyEvent.ACTION_DOWN) {Log.i(TAG, "dispatchKeyEvent() WheelListview ACTION_DOWN===="+ event.getKeyCode());switch (keyCode) {case KeyEvent.KEYCODE_DPAD_DOWN:smoothScrollBy(mItemHeight, 200);proSelection = curSelection;curSelection = getSelection();if (handler.hasMessages(HANDLER_LISTENER)) {handler.removeMessages(HANDLER_LISTENER);} else {if (proSelection == curSelection) {handler.sendEmptyMessage(LAST_HANDLER_LISTENER);}}setSelection(getSelection()); // solve the system bugreturn true;case KeyEvent.KEYCODE_DPAD_UP:smoothScrollBy(-mItemHeight, 200);proSelection = curSelection;curSelection = getSelection();if (handler.hasMessages(HANDLER_LISTENER)) {handler.removeMessages(HANDLER_LISTENER);} else {if (proSelection == curSelection) {handler.sendEmptyMessage(LAST_HANDLER_LISTENER);}}setSelection(getSelection()); // solve the system bugreturn true;default:break;}}return super.dispatchKeyEvent(event);}private Handler handler = new Handler() {public void handleMessage(android.os.Message msg) {switch (msg.what) {case HANDLER_LISTENER:mItemSelectedListener.onItemSelected(getSelection());break;case LAST_HANDLER_LISTENER:mItemSelectedListener.onItemSelected(getSelection());break;default:break;}};};/*** @param mItemSelectedListener*/public void setOnWheelItemSelectedListener(WheelItemSelectedListener mItemSelectedListener) {this.mItemSelectedListener = mItemSelectedListener;}@Overridepublic void setAdapter(ListAdapter adapter) {super.setAdapter(adapter);mDataSize = adapter.getCount() - mWheelSize + 1;initView();}public void setAdapter(ListAdapter adapter, int dataSize) {super.setAdapter(adapter);mDataSize = dataSize;initView();}public void recover() {if (getSelection() == 0) return;smoothScrollToPosition(0);isRecover = true;}public int getSelection() {if (mCurrentPositon == 0) {mCurrentPositon = mWheelSize / 2;}Log.d("TAG", "WheelListView mCurrentPositon ===" + mCurrentPositon);return (mCurrentPositon - mWheelSize / 2) % mDataSize;}/*** @param wheelSize*/public void setWheelSize(int wheelSize) throws WheelListViewException {if (mWheelSize % 2 != 1) {throw new WheelListViewException("wheelSize must be odd number!");}mWheelSize = wheelSize;}public class WheelListViewException extends Exception {private static final long serialVersionUID = 1L;public WheelListViewException(String detailMessage) {super(detailMessage);}}private void initView() {mItemHeight = measureHeight();ViewGroup.LayoutParams lp = getLayoutParams();lp.height = mItemHeight * mWheelSize;}private int measureHeight() {ListAdapter adapter = getAdapter();// 容错if (adapter == null || adapter.getCount() == 0) {return 0;}View itemView = adapter.getView(0, null, this);itemView.measure(0, 0);return itemView.getMeasuredHeight();}/*** * @author puky* @param position*            return real listview item position* */public interface WheelItemSelectedListener {public void onItemSelected(int position);}public void setSeletedSolidColor(int color){this.seletedSolidColor = color;seletedSolidPaint.setColor(color);}public void setLabelSelectColor(int color){this.mLabelSelectColor = color;}public void setLabelColor(int color){this.mLabelColor = color;}}
package com.pu.test;import java.util.List;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;public abstract class WheelListAdapter<T> extends BaseAdapter{private int mWheelSize = 3;protected Context context;protected List<T> listData;protected LayoutInflater layoutInflater;public WheelListAdapter(Context context, List<T> listData, int wheelSize) {this.context = context;this.listData = listData;this.mWheelSize = wheelSize;layoutInflater = LayoutInflater.from(context);}@Overridepublic int getCount() {return listData.size() + mWheelSize - 1;}@Overridepublic Object getItem(int position) {if (position < mWheelSize / 2|| position >= listData.size() + mWheelSize / 2) {return null;} else {return listData.get((position - mWheelSize / 2) %listData.size());}}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {Object holder = null;if (convertView == null) {convertView = buildConvertView(layoutInflater);holder = buildHolder(convertView);convertView.setTag(holder);} else {holder = convertView.getTag();}if (position < mWheelSize / 2|| position >= listData.size() + mWheelSize / 2) {invisibleView(holder, convertView);} else {position = (position - mWheelSize / 2) % listData.size();visibleView(holder, convertView, position);}return convertView;}public void setData(List<T> data){this.listData = data;}protected abstract Object buildHolder(View convertView);protected abstract View buildConvertView(LayoutInflater layoutInflater);/*** 条件满足视图不可见时调用*/protected abstract void invisibleView(Object viewHolder, View convertView);/*** 条件满足视图可见时调用* @param position*/protected abstract void visibleView(Object viewHolde, View convertView, int position);}

android tv 焦点居中自定义listview控件的实现相关推荐

  1. android 自定义listview控件,一个简单又完整的自定义ListView

    ListView 一.简单列表 1.在activity_main中添加控件ListView xmlns:tools="http://schemas.android.com/tools&quo ...

  2. android程序日历layout,Android使用GridLayout绘制自定义日历控件

    效果图 思路:就是先设置Gridlayout的行列数,然后往里面放置一定数目的自定义日历按钮控件,最后实现日历逻辑就可以了. 步骤: 第一步:自定义日历控件(初步) 第二步:实现自定义单个日期按钮控件 ...

  3. Android自定义组合布局,Android 流式布局 + 自定义组合控件

    自定义组合控件 package yanjupeng.bawei.com.day09.two; import android.content.Context; import android.util.A ...

  4. android tv github,GitHub - dongbingliu/Android-tv-widget: Android tv,盒子,投影仪 控件

    [前言] 因为要加强 Android 投影仪的 luncher 倒影国际化的功能,所以开始的时候在BroderView的基础改了些东西. 后来又一些BUG,修复了,感觉毕竟是用的别人的开源代码,如果不 ...

  5. Android开发学习笔记-自定义组合控件

    为了能让代码能够更多的复用,故使用组合控件.下面是我正在写的项目中用到的方法. 1.先写要组合的一些需要的控件,将其封装到一个布局xml布局文件中. <?xml version="1. ...

  6. android tv nugat,GitHub - GongXunYoung/Android-tv-widget: Android tv,盒子,投影仪 控件

    Android TV 开发框架 QQ群:522186932 Leanback 框架(类似谷歌的Leanback,更简直,更方便): 键盘框架: 菜单框架: 整体目录结构 *AndroidTvWidet ...

  7. (转)android UI进阶之自定义组合控件

    第一个实现一个带图片和文字的按钮,如图所示: 整个过程可以分四步走.第一步,定义一个layout,实现按钮内部的布局.代码如下: [html] view plaincopy <?xml vers ...

  8. android 一分钟倒计时动画,Android利用属性动画自定义倒计时控件

    本文介绍一下利用属性动画(未使用Timer,通过动画执行次数控制倒计时)自定义一个圆形倒计时控件,比较简陋,仅做示例使用,如有需要,您可自行修改以满足您的需求.控件中所使用的素材及配色均是笔者随意选择 ...

  9. Android 自定义ListView控件,滑动删除

    1.触摸事件 dispatchTouchEvent 判断是否处理触摸动作 onTouchEvent 处理触摸动作 2.Android对于控制和获取View在屏幕很强大 ListView: pointT ...

最新文章

  1. “西南偏南” 三十年首次 “聚焦中国”
  2. LeetCode题 - 26 删除排序数组中的重复项 python实现
  3. libx264进行视频编码的流程
  4. 数据:灰度增持3594枚LTC和1.43万枚LINK
  5. 书屋(一):读《世界是平的》有感
  6. 创建CocoaPods的Framework Swift组件化之路(上)
  7. SAS入门(二)---DATA步
  8. 有关js获取屏幕宽度问题
  9. 【教程】Tomcat 的catalina.out 日志按照自定义日期格式进行切割
  10. 新文件泄露更多NSA卫星监听站的信息
  11. 《个人信息安全规范 (2019-6-21) 》征求意见稿的最新变化
  12. 国内使用php谷歌翻译_中英文谷歌翻译-PHP
  13. 入门-误差逆传播算法
  14. 触动人心:如何设计优秀的iPhone应用
  15. Mac OS X TextMate 运行 OCaml代码提示出错
  16. 2017计科01-08编译原理模拟测试2--chap03
  17. oracle8i误删除临时表空间后的恢复
  18. python:实现RGB和HSV相互转换算法(附完整源码)
  19. netcat基本使用方法总结
  20. 逐渐成熟 Intel VT技术性能初探

热门文章

  1. 第十九章 Bellman-Ford算法(由SPFA算法逆推BF,独特解读,超级详细)
  2. 【LeetCode】601.体育馆的人流量
  3. ES文件传输助手1.0.0
  4. 卓易修改运动步数的php源码_利用卓易健康接口实现微信运动步数的修改
  5. 【蓝桥杯省赛真题3】Scratch游泳倒计时 少儿编程scratch蓝桥杯选拔赛真题讲解
  6. mysql5.7 win2003安装_mysql 5.7 win系统安装
  7. 为何手机5G有时候比4G还慢?
  8. 中国海洋大学计算机系实习报告,中国海洋大学 海洋学实习报告
  9. 得得模板首页快照劫持、被篡改被挂马、被入侵解决方法
  10. 如何正确使用电动牙刷?——NuonaSmile诺娜