安卓TV开发(五) 移动智能终端UI之实现主流TV焦点可控UI
;
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主要通过用TV播放器的实现去了解下在智能设备上的开发一个APP,实现遥控器控制焦点移动,方向键模拟鼠标,并在线完成视频直播,手机当遥控器使用等相关功能。此UI也适用于车载设备和移动智能家具设备,。
上一篇中 安卓TV开发(四)实现主流智能TV视频播放器UI 初步学习了智能电视上UI的设计,且完成了在电视上可以控制的自定义View(FocusView),在上篇结尾中提到,他并不适合在手机上使用,并且也没实现点击遥控键(KEYCODE_DPAD_CENTER)的item事件,因此本篇也将会继续完善和修复此存在的问题。
一,实现遥控器Ok键Item点击事件
1 通过观察GridView的源码其实继承了 AdapterView<T
extends Adapter>这个类,其中的item事件也是实现此类的 OnItemClickListener接口
源码如下:
/**
* Interface definition for a callback to be invoked when an item in this
* AdapterView has been clicked.
*/
public interface OnItemClickListener {
/**
* Callback method to be invoked when an item in this AdapterView has
* been clicked.
* <p>
* Implementers can call getItemAtPosition(position) if they need
* to access the data associated with the selected item.
*
* @param parent The AdapterView where the click happened.
* @param view The view within the AdapterView that was clicked (this
* will be a view provided by the adapter)
* @param position The position of the view in the adapter.
* @param id The row id of the item that was clicked.
*/
void onItemClick(AdapterView<?> parent, View view, int position, long id);
}
以上代码不难理解 AdapterView 提供个一个回调,在我们所用到的actity中或view去实现此接口。本次可以复用以上接口,使开发者习惯安卓系统的条目监听。但由于我们的view并未继承AdapterView,而在回调必须要传入一 AdapterView的父类,因此我们自定义自己的监听器。下面我们也给FocsView定义一个条目点击事件监听器。
/**
* FocusView
* Interface definition for a callback to be invoked when an item in this
* FocusView item has been clicked.
* @author liuyongkui.
* @param <T>
*/
public interface OnItemClickListener<T> {
/**
* @param mFocusView
* FoucusView.
* @param view
* focus View item.
* @param col
* col Num.
* @param row
* row Num.
* @param id
* item id.
*/
void onItemClick(FocusView mFocusView, View focusView,
FocusItemModle<TvModle> focusItem, int Postion, int row, int col, int id);
}
同理为了开发者自定义效果,我们也为focusVIew的子条目心新增一个选中事件监听器。
/**
* FocusView
* Interface definition for a callback to be invoked when an item in this
* FocusView item has been Selected.
* @author liuyongkui.
* @param <T>
*/
public interface OnItemSelectedListener <T> {
/**
* @param <T>
* @param mFocusView
* FoucusView.
* @param view
* focus View item.
* @param col
* col Num.
* @param row
* row Num.
* @param id
* item id.
*/
public void onItemSelected(FocusView metroView, View view, T modle, int col, int row, long id);
}
2 上列代码发现我用了一个接口泛型,此处原因是为了提高接口的扩展性,比如 开发中你定义的MusicModle ,以上的监听就会有问题了 会拋类型转换异常,因此当我们再给接口传什么Modle,回调的时候就会返回相应的modle.
等完成了接口圆原型,再定义他为FocusView内部属性
/** items mOnItemSelectedListener*/ private OnItemSelectedListener mOnItemSelectedListener; /** items mOnItemClickListener */ private OnItemClickListener mOnItemClickListener;
@SuppressWarnings("rawtypes")
public OnItemSelectedListener<?> getmOnItemSelectedListener() {
return mOnItemSelectedListener;
}
public void setOnItemSelectedListener(
OnItemSelectedListener mOnItemSelectedListener) {
this.mOnItemSelectedListener = mOnItemSelectedListener;
}
3 完成以上工作,考虑在何时调用此接口,通过要实现的目标就能知道答案,肯定是在用户点击Ok键时触发此回调,接着重写onKeyUp()事件
继续完善上篇中已实现的上下左右的键盘事件,
case KeyEvent.KEYCODE_DPAD_CENTER:
if(mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(this,
focusItem.getFocusView(), focusItem, focusItem.getPostion(), focusItem.getRow(), focusIt em.getCol(), focusItem.getId());
}
break;
}
当然到这里代码肯定会有报错,因此我的FocusITemModle 也要随之加入Position,ID等 由于本次代码无adapter,因此我个人是在focusView布局时setPosition,Id也是通通过行数和列数决定,
4 在activty中去实现此接口 ,和一般的listVIew,GrideVIew一样,但是导包必须到导入focusView的OnItemClickListener事件,值得注意的是 实现点此接口时需要我们传入object,在这里我传入: FocusItemModle<TvModle> ,和上篇有很大区别的是这次的FocusItemModle也适用了泛型,目的是为了代码灵活性。
FocusUIActivity extends Activity implements OnItemClickListener<FocusItemModle<TvModle>>
FocusView view = (FocusView) findViewById(R.id.focus_ui); view.setOnItemClickListener(this);
实现接口中的onItemClick()方法:
@Override
public void onItemClick(FocusView mFocusView, View focusView,
FocusItemModle<TvModle> focusItem, int Postion, int row, int col,
long id) {
Toast.makeText(getApplicationContext(), "row:" + row + "col:" + col, 1).show();
}
通过以上三步我们完善了在按键Ok时触发的事件,同理选择事件并不需要我们按键触发,而是当Item获得焦点时触发,学习了前面知识的朋友。一定会觉得再简单不过了,这里留给读者去完善。
二,实现触屏Item点击事件
到此我们的代码还并不支持触摸点击,为了兼容手机我为此去实现触摸时触发onItemClick(),所以今天的难点又来了 因为我已经屏蔽了系统的触摸事件,因此继续完善OnTucH事件,首先拿到这样的一个实现需求,我们首先要想到
当用户点击屏幕的模块区域时通过点击的坐标,然后得到当我所在的view,然后触发OnItemClick()即可
1 首先写一个工具类,专门通过X,Y去获取点击的最小View
public final class ViewUtil {
private ViewGroup view;
private static Context context;
/** instance */
private static ViewUtil sInstance;
private ViewUtil(ViewGroup view) {
this.view = view;
this.context =view.getContext();
}
/**
* @return instance
*/
public static synchronized ViewUtil getInstance(ViewGroup view) {
if (sInstance == null) {
sInstance = new ViewUtil(view);
}
return sInstance;
}
/**
* get Onclicked ItemView
* 在重写ViewGroup使用
*
* @param X
* @param Y
* @return getView
*/
public static View getViewAtActivity(int x, int y) {
// 从Activity里获取容器
View root =((Activity)context).getWindow().getDecorView();
return findViewByXY(root, x, y);
}
/**
* get Onclicked ItemView
* 在重写ViewGroup使用
*
* @param X
* @param Y
* @return getView
*/
public View getViewAtViewGroup(int x, int y) {
return findViewByXY(view, x, y);
}
/**
* @param view
* @param x
* @param y
* @return
*/
private static View findViewByXY (View view, int x, int y) {
View targetView = null;
if (view instanceof ViewGroup) {
// 父容器,遍历子控件
ViewGroup v = (ViewGroup) view;
for (int i = 0; i < v.getChildCount(); i++) {
targetView = findViewByXY(v.getChildAt(i), x, y);
if (targetView != null) {
break;
}
}
} else {
targetView = getTouchTarget(view, x, y);
}
return targetView;
}
/**
* @param view
* @param x
* @param y
* @return
*/
private static View getTouchTarget(View view, int x, int y) {
View targetView = null;
ArrayList<View> TouchableViews = view.getTouchables();
for (View child : TouchableViews) {
if (isTouchPointInView(child, x, y)) {
targetView = child;
break;
}
}
return targetView;
}
/**
* 判断view是否可以聚焦
* @param view
* @param x
* @param y
* @return
*/
private static boolean isTouchPointInView(View view, int x, int y) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0];
int top = location[1];
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
if (view.isClickable() && y >= top && y <= bottom && x >= left
&& x <= right) {
return true;
}
return false;
}
}
2 当拿到当前的view了,但是回调需要一个Modle,因此定义一个集合,用来将view和model一一对应,当返回view,我们可以在集合里去拿出model,在FocuasView初始化onLayout布局的时,去初始化此集合。当触摸事件并且移动距离为零时,触发回调,因为上篇已经实现了触摸滑动翻页功能。
PS:转载请标明出处:http://blog.csdn.net/sk719887916,
//将点击的view和item modle关联起来 protected HashMap< View, FocusItemModle<TvModle>> maps = new HashMap<View, FocusItemModle<TvModle>>();
初始化maps
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int itemCount = mFocusItems.size();
if (itemCount != getChildCount())
throw new IllegalArgumentException("contain unrecorded child");
for (int i = 0; i < itemCount; i++) {
final FocusItemModle item = mFocusItems.get(i);
final View childView = item.getFocusView();
maps.put(childView ,item);
if (childView.getVisibility() != View.GONE) {
final int childLeft = getPaddingLeft() + (mColWidth + mGapWidth) * item.getCol();
final int childTop = getPaddingTop() + (mRowHeight + mGapHeight) * item.getRow();
final int childWidth = (mColWidth + mGapWidth) * item.getColSpan() - mGapWidth;
final int childHeight = (mRowHeight + mGapHeight) * item.getRowSpan() - mGapHeight;
childView.layout(childLeft, childTop, childLeft + childWidth,
childTop + childHeight);
}
}
}
3 onTouchEvent()事件里继续处理触摸非滑动逻辑,在第二篇 MotionEvent.ACTION_UP 代码中完善,实现回调onItemClick()
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity();
int velocityY = (int) velocityTracker.getYVelocity();
int row = mCurRow;
int col = mCurCol;
if (velocityX > SNAP_VELOCITY && mCurCol > 0) {
col--;
} else if (velocityX < -SNAP_VELOCITY && mCurCol < mColsCount - 1) {
col++;
}
if (velocityY > SNAP_VELOCITY && mCurRow > 0) {
row--;
} else if (velocityY < -SNAP_VELOCITY && mCurRow < mRowsCount - 1) {
row++;
}
if (row == mCurRow && col == mCurCol) {
if (velocityX == 0 && velocityY == 0) {
snapToDestination();
}
else {
mOnX = (int) event.getRawX();
mOnY = (int) event.getRawY();
Log.e(TAG, "rawX =" +mOnX + " rawY ="+ mOnY);
//sendMotionEvent(mOnX,mOnY, KeyEvent.ACTION_UP);
View onClickItem = getViewAtViewGroup(mOnX,mOnY);
FocusItemModle<TvModle> onClickItemModle = maps.get(onClickItem);
if(mOnItemClickListener != null && onClickItem != null) {
Log.e(TAG, "View 2=" +mOnX + " rawY 2 ="+ mOnY);
row = onClickItemModle.getCol();
col = onClickItemModle.getCol();
mOnItemClickListener.onItemClick(this,
onClickItem, onClickItemModle,
onClickItemModle.getRow(), onClickItemModle.getPostion(), onClickItemModle.getCol(), onClickItemModle.getId());
snapTo(row, col);
}
}
}
else {
snapTo(row, col);
if (mFocusListener != null)
mFocusListener.scrollto(row, col);
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mTouchState = TOUCH_STATE_REST;
break;
到此一切就绪了,模拟器效果:
效果:
综合以上代码 ,发现我们的View还是存在缺陷,上文就发现,因为没有adapter,导致我们的代码繁琐,并且也不符合一个集合框架(listView,GridView)的开发习惯和
实现逻辑。下篇我会通过上面的代码来优化,完成FcousView和Adapter结合,完成高效的UI框架,使得开发者自定义宽高,翻页动画,显示可见条数,和子item布局,下篇也会成为就是TV视图文章中的终结篇,之后将进行多媒体和直播流的相关知识,欢迎大家阅读。
转载请标明出处:http://blog.csdn.net/sk719887916,作者:skay
源码下载:https://github.com/Tamicer/FocusView
更多技术文章请关注本人微信公众号:
安卓TV开发(五) 移动智能终端UI之实现主流TV焦点可控UI的更多相关文章
- 安卓TV开发(三) 移动智能设备之实现主流TV电视盒子焦点可控UI
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...
- 安卓TV开发(六) 移动智能终端UI之实现类似GridView的焦点控制FocusView框架
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40045089,作者:skay 前言 安卓TV开发(五) 移动智能终端UI之实现主流 ...
- 安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源
载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 结束了所有UI绘制的学习,智能设备常用的应用音视频类, ...
- 安卓TV开发(八) 移动智能终端多媒体爬虫技术 获取加载网页视频源
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 从上一篇学习中,学习了多媒体技术中的怎么去用josu ...
- 安卓TV开发(四) 实现主流智能TV视频播放器UI
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...
- 安卓TV开发(十) 智能电视开发之在线视频直播
转载注明出处:http://blog.csdn.net/sk719887916/article/details/46582987 从<安卓TV开发(八) 移动智能终端多媒体之在线加载网页视频源& ...
- 安卓Tv开发(一)移动智能电视之焦点控制(触控事件)
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验风格上有很大的区别,本系列博 ...
- 安卓TV开发(概述) 智能电视之视觉设计和体验分析
转载说明出处 :http://blog.csdn.net/sk719887916, 作者:skay 前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大 ...
- 安卓TV开发(九) Android模拟事件 遥控器变身成鼠标来操作TV
本文出处:http://blog.csdn.net/sk719887916/article/details/40348853,作者:skay 阅读此文建议先阅读 安卓Tv开发(二)移动智能电 ...
随机推荐
- ACM Doing Homework again
Ignatius刚刚从第30届ACM / ICPC回到学校.现在他有很多作业要做.每个老师给他一个截止作业的截止日期.如果Ignatius在截止日期之后进行了家庭作业,老师将减少他的最终考试成绩.现在 ...
- 使用POI实现报表打印功能
[版权申明:本文系作者原创,转载请注明出处] 文章出处:http://blog.csdn.net/sdksdk0/article/details/53393453 作者:朱培 ID:sdksdk0 这 ...
- 大规模WebGL应用引发浏览器崩溃的几种情况及解决办法
一般的Web应用基本上不会导致浏览器崩溃,写Javascript代码也不需要管理内存资源,基本也不需要考虑内存"泄露"的问题.随着H5的崛起,越来越多的原本在桌面端的软件也改头换面 ...
- 深度学习与计算机视觉系列(3)_线性SVM与SoftMax分类器
作者: 寒小阳 &&龙心尘 时间:2015年11月. 出处: http://blog.csdn.net/han_xiaoyang/article/details/49949535 ht ...
- 微信小程序实例-摇一摇抽奖
概述 前面我们讲了如何开始微信小程序搭建和一些组件的介绍.微信小组件和微信小程序入门 微信小程序目录 为了更好的理解小程序和小程序开发,我们首先来看一下项目的目录. 首先看下根目录下的app.json ...
- Android Studio下导出jar包和aar包
Android Studio下导出jar包和aar包 jar包和aar包的区别 步骤 1. 创建Android工程 创建工程比较简单,不错复述 2. 创建一个Library(Module) 创建了一个 ...
- Android快速关联V4包的方式
很多时候需要管理v4包,当然有很多种办法去关联.本人觉得最快速的方式,是通过添加配置文件的方式.只需要ctrl+c和ctrll+v就能解决了 方法如下: 1.新建一个android-support-v ...
- Java学习之运算符使用注意的问题
运算符使用注意的问题 运算符(掌握) (1)算术运算符 A:+,-,*,/,%,++,-- B:+的用法 a:加法 b:正号 c:字符串连接符 C:/和%的区别 数据做除法操作的时候,/取得是商,%取 ...
- 21 ViewPager RadioGroup
结构 MainActivity.java package com.qf.day21_viewpagerfragmentrg_demo4; import java.util.ArrayList; imp ...
- RxJava在Android中使用场景详解
RxJava 系列文章 <一,RxJava create操作符的用法和源码分析> <二,RxJava map操作符用法详解> <三,RxJava flatMap操作符用法 ...