本文出处:http://blog.csdn.net/sk719887916/article/details/40348853,作者:skay
     接触TV的的开发,操作体验是我们开发者必须要关注的,因此,遥控器做鼠标成了智能设备的开发主流不可缺少的一部分,今天给大家带来怎样实现遥控操作模拟鼠标的功能,觉得ok的请到下方自觉的点个赞。有自动化测试框架也有用此方式实现的。
   实现功能: 遥控器方向键控制鼠标箭头移动
                       遥控器可以操作模拟鼠标进行点击操作。
  效果图:

    实现之前先做个大致分析,记得之前我在一篇安卓实现高仿Ios桌面的博客中就接触过类似此需求的功能,第一眼大家觉得可拖动gridview和实现遥控器拖动鼠标有啥关系,仔细一分析,其实异曲同工,可拖动的gridView我们是用手指进行item移动的,而这次的模拟鼠标我们可以把它想成一个item(只不过展现的UI为一个鼠标箭头的图片),用遥控器进行控制的,当然我们无需拖动,用遥控方向键来控制item的位移即可。至于遥控器点击OK键进行点击执行点击操作的事件,我们可以获取模拟鼠标在屏幕中的绝对坐标,然后模拟一个此处的点击事件即可。

一 自定义鼠标视图

        用于展现鼠标的具体视图,MouseView 继承 FrameLayout ,之后漂浮在控制的activity上,只要由WindowManage控制此视图的展现和移动。
public MouseView(Context context) {
		super(context);
	}

	public MouseView(Context context, MouseManager mMouseMrg) {
		super(context);
		init( mMouseMrg);
	}

	public OnMouseListener getOnMouseListener() {
		return mOnMouseListener;
	}

	public void setOnMouseListener(OnMouseListener mOnMouseListener) {
		this.mOnMouseListener = mOnMouseListener;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		if (mMouseView != null && mMouseBitmap != null) {
			mMouseView.measure(MeasureSpec.makeMeasureSpec(mMouseBitmap.getWidth(), MeasureSpec.EXACTLY),
					MeasureSpec.makeMeasureSpec(mMouseBitmap.getHeight(), MeasureSpec.EXACTLY));
		}
	}

	private void init(MouseManager manager) {
		mMouseManager  = manager;
		Drawable drawable =	getResources().getDrawable(
				R.drawable.shubiao);
		mMouseBitmap = drawableToBitamp(drawable);
		mMouseView = new ImageView(getContext());
		mMouseView.setImageBitmap(mMouseBitmap);
		addView(mMouseView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		mOffsetX = (int)((mMouseBitmap.getWidth()));
		mOffsetY = (int)((mMouseBitmap.getHeight()));

	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		if(mMouseView != null) {
			mMouseView.layout(mMouseX, mMouseY, mMouseX + mMouseView.getMeasuredWidth(), mMouseY + mMouseView.getMeasuredHeight());
		}
	}

二 鼠标控制器

      用来处理点击事件,和鼠标移动事件。主要是重写事件分发方法 dispatchKeyEvent(KeyEvent event),用于控制鼠标移动和点击。
   
/**
	 * @param parent
	 * @param type
	 */
	public void init(ViewGroup parentView, int type) {
		mParentView = parentView;
		mContext = parentView.getContext();
		mMouseView = new MouseView(mContext, this);
		mMouseView.setOnMouseListener(this);
		mCurrentType = type;
	}

	/**
	 * @return
	 */
	public boolean getMouseType() {

		return isMouseType;
	}

	/**
	 * @return
	 */
	public int getCurrentActivityType() {
		return mCurrentType;
	}

	/**
	 * showmouse
	 */
	public void showMouseView() {
		ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
				ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);

		if(mMouseView != null) {
			mParentView.addView(mMouseView, lp);
		}
	}
	public boolean onDpadClicked(KeyEvent event) {

		if(!isMouseType) {
			return false;
		}

		if(event.getKeyCode() == KEYCODE_CENTER) {
			dispatchKeyEventToMouse(event);
		} else {
			if(event.getAction() == KeyEvent.ACTION_DOWN) {
				if(!isKeyEventCousumed) {

					if(event.getDownTime() - mLastEventTime < defTimes) {

						if(mSpeed < defMaxSpeed) {
							mSpeed ++;
						}
					} else {
						mSpeed = 1;
					}
				}
				mLastEventTime = event.getDownTime();
				dispatchKeyEventToMouse(event);
				isKeyEventCousumed = true;
			} else if(event.getAction() == KeyEvent.ACTION_UP) {
				if(!isKeyEventCousumed){
					dispatchKeyEventToMouse(event);
				}
				isKeyEventCousumed = false;
			}
		}
		return true;
	}

	public void sendCenterClickEvent(int x, int y, int action) {
		sendMotionEvent(x, y, action);
	}

	@SuppressLint("InlinedApi")
	public void sendMouseHoverEvent(int downx, int downy) {
		sendMotionEvent(downx, downy, MotionEvent.ACTION_HOVER_MOVE);
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
	@SuppressLint("NewApi")
	private void sendMotionEvent(int x, int y, int action) {

		MotionEvent motionEvent = getMotionEvent( x, y ,action) ;

		if(action == MotionEvent.ACTION_HOVER_MOVE) {
			motionEvent.setSource(InputDevice.SOURCE_CLASS_POINTER);
			mMouseView.dispatchGenericMotionEvent(motionEvent);
			//mParentView.dispatchGenericMotionEvent(motionEvent);
		} else {
			//mParentView.dispatchTouchEvent(motionEvent);
			mMouseView.dispatchTouchEvent(motionEvent);
		}
	}

	private  MotionEvent getMotionEvent(int downx, int downy, int action) {
		// TODO Auto-generated method stub
		long downTime = SystemClock.uptimeMillis();
		long eventTime = SystemClock.uptimeMillis();
		int metaState = 0;
		return MotionEvent.obtain(
				downTime,
				eventTime,
				action,
				downx,
				downy,
				metaState
				);
	}

	@Override
	public boolean onclick(View v, KeyEvent et) {
		if (getMouseType()) {

			return onDpadClicked(et);
		}
		return mParentView.dispatchKeyEvent(et);
	}
   

三 当前页面加入鼠标

         如果某个activity需要加入鼠标,只要初始化 MouseManager,然后设置当先鼠标显示即可,本次为了方便操作和直观的显示,加入一个webView加载网页,用于鼠标控制操作。
public class MainActivity extends Activity {
	WindowManager wm;
	WindowManager.LayoutParams params;

	private MouseManager mMouseManager;

	public static ViewGroup contentView;
	private WebView webView;

	private View mLoginStatusView;

	private TextView mLoaddingMessageView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		LayoutInflater inflater = getLayoutInflater();
		contentView = (ViewGroup) inflater.inflate(R.layout.test, null);
		setContentView(contentView);
		init();
		initMouse();
		showMouse();

	}

	private void init() {

		webView = (WebView) contentView.findViewById(R.id.web);
		mLoginStatusView = this.findViewById(R.id.login_status);
		mLoaddingMessageView = (TextView) this
				.findViewById(R.id.login_status_message);
		Button button = (Button) contentView.findViewById(R.id.btn_onclick);
		button.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Toast.makeText(MainActivity.this, "onclicked ", 1).show();
				showProgress(true);
				webView.setVisibility(View.VISIBLE);

				webView.loadUrl("https://www.baidu.com/");
				WebSettings settings = webView.getSettings();
				settings.setJavaScriptEnabled(true);
				webView.setWebViewClient(new WebViewClient() {
					@Override
					public boolean shouldOverrideUrlLoading(WebView view,
							String url) {

						view.loadUrl(url);
						return true;
					}

					@Override
					public void onPageFinished(WebView view, String url) {

						super.onPageFinished(view, url);
					}

					@Override
					public void onReceivedError(WebView view, int errorCode,
							String description, String failingUrl) {
						Toast.makeText(MainActivity.this, "加载失败 ",
								Toast.LENGTH_LONG).show();
						super.onReceivedError(view, errorCode, description,
								failingUrl);
					}

				});

				webView.setWebChromeClient(new WebChromeClient() {
					@Override
					public void onProgressChanged(WebView view, int newProgress) {
						// TODO Auto-generated method stub
						if (newProgress == 100) {

							showProgress(false);

						} else {

						}

					}

				});

			}
		});

	}

	@SuppressLint("NewApi")
	private void showProgress(final boolean show) {
		// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
		// for very easy animations. If available, use these APIs to fade-in
		// the progress spinner.
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
			int shortAnimTime = getResources().getInteger(
					android.R.integer.config_shortAnimTime);

			mLoginStatusView.setVisibility(View.VISIBLE);
			mLoginStatusView.animate().setDuration(shortAnimTime)
					.alpha(show ? 1 : 0)
					.setListener(new AnimatorListenerAdapter() {
						@Override
						public void onAnimationEnd(Animator animation) {
							webView.setVisibility(show ? View.VISIBLE
									: View.GONE);
						}
					});

			webView.setVisibility(View.VISIBLE);
			webView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1)
					.setListener(new AnimatorListenerAdapter() {
						@Override
						public void onAnimationEnd(Animator animation) {
							webView.setVisibility(show ? View.GONE
									: View.VISIBLE);
						}
					});
		} else {
			// The ViewPropertyAnimator APIs are not available, so simply show
			// and hide the relevant UI components.
			mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
			webView.setVisibility(show ? View.GONE : View.VISIBLE);
		}
	}

	private void showMouse() {

		mMouseManager.showMouseView();

	}

	public void initMouse() {
		initMouseMrg();
	}

	public void initMouseMrg() {

		mMouseManager = new MouseManager();
		mMouseManager.init(contentView, MouseManager.MOUSE_TYPE);
		mMouseManager.setshowMouse(true);

	}

}

简单三步,代码就可以实现简单的用于遥控器操作的TV的浏览器,本次demo只是用户手动模拟点击,至于实现自动化模拟点击,以上方式显得极为笨拙,

  如果实现自动化测试或者模拟点击事件 ,采用Instrumentation代理,而Instrumentation不需要一定的activity展现,我, 10, 0));
  • inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 10, 10, 0));
  • 具体逻辑是当我的app启动时 。就可以用全局的Instrumentation来监控到底当前在哪个界面,提前将点击的入口定义OK后,接下来的点击事件就如同用户主动点击一样,包括跳转逻辑不再需要考虑。