载请标明出处:http://blog.csdn.net/sk719887916,作者:skay
   由于其他网站收录,导致你无法查看本系列原创文章请点击此处 安卓TV开发(四)实现主流智能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的更多相关文章

  1. 安卓TV开发(三) 移动智能设备之实现主流TV电视盒子焦点可控UI

    前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...

  2. 安卓TV开发(六) 移动智能终端UI之实现类似GridView的焦点控制FocusView框架

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40045089,作者:skay 前言 安卓TV开发(五) 移动智能终端UI之实现主流 ...

  3. 安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源

    载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 结束了所有UI绘制的学习,智能设备常用的应用音视频类, ...

  4. 安卓TV开发(八) 移动智能终端多媒体爬虫技术 获取加载网页视频源

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 从上一篇学习中,学习了多媒体技术中的怎么去用josu ...

  5. 安卓TV开发(四) 实现主流智能TV视频播放器UI

    前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...

  6. 安卓TV开发(十) 智能电视开发之在线视频直播

    转载注明出处:http://blog.csdn.net/sk719887916/article/details/46582987 从<安卓TV开发(八) 移动智能终端多媒体之在线加载网页视频源& ...

  7. 安卓Tv开发(一)移动智能电视之焦点控制(触控事件)

    前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验风格上有很大的区别,本系列博 ...

  8. 安卓TV开发(概述) 智能电视之视觉设计和体验分析

         转载说明出处 :http://blog.csdn.net/sk719887916, 作者:skay 前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大 ...

  9. 安卓TV开发(九) Android模拟事件 遥控器变身成鼠标来操作TV

    本文出处:http://blog.csdn.net/sk719887916/article/details/40348853,作者:skay      阅读此文建议先阅读 安卓Tv开发(二)移动智能电 ...

随机推荐

  1. JAVA中的常量定义在class中还是interface中比较合理?

    本文地址:http://blog.csdn.net/sushengmiyan 本文作者:苏生米沿 java中使用的常量可以集中定义在一个文件中. 有两种解决方案: 1.在Constants.java中 ...

  2. 计算机网络之IP协议族

    网际协议IP 与IP协议配套使用的还有三个协议: 地址解析协议 ARP   (Address Resolution Protocol) 网际控制报文协议 ICMP  (Internet Control ...

  3. hadoop入门级总结三:hive

    认识hive  Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供完整的SQL查询功能,可以将SQL语句转换为MapReduce任务运行  Hive是建立在 ...

  4. 从Cell类型转变成数据型

    我们有一个如下的cell数据 cdata = {'1' '11' '111' '1111' '11111'}; 现在要把他转变成double型的数组,很自然会想到的方法是cell2mat,可悲的是会遇 ...

  5. Git运用基础之如何删除Github上不想要的项目

    今天突然想删除,(强迫症想删除)之前练习时多创建的多个Github上的源代码或者无用Demo地址,然后看了一些文章都比较老式,这里我展示一下最新的删除步骤. 一.首先登录自己的Github账户主页(没 ...

  6. EXT JS认识EXTJS,第一个EXTJS例子

    大部分内容转载自:http://blog.csdn.net/wanghuan203/article/details/8011112 和http://www.cnblogs.com/willick/p/ ...

  7. Ajax框架,DWR介绍,应用,例子

    使用Ajax框架 1. 简化JavaScript的开发难度 2. 解决浏览器的兼容性问题 3. 简化开发流程 常用Ajax框架 Prototype 一个纯粹的JavaScript函数库,对Ajax提供 ...

  8. Maven 介绍、安装使用

    简介         Maven是一个强大的构建工具,能够帮我们自动化构建过程,从清理.编译.测试到生成报告,再到打包和部署.只要使用Maven配置好项目,然后执行命令(如mvn clean inst ...

  9. Dynamics CRM 安装Microsoft Dynamics CRM Reporting Extensions

    在装完CRM Server 后这个组件是必须安装的,但今天由于我的大意在客户安装生产环境时,告诉客户这个组件装在APP服务器上,导致客户安装时 SSRSInstance怎么都是空的,害的人家找了半天原 ...

  10. (NO.00005)iOS实现炸弹人游戏(八):游戏主角(一)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 最近一直在做另一个RPG游戏,所以本系列迟迟没有更新,上一篇博 ...