900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 安卓TV开发(三) 移动智能设备之实现主流TV电视盒子焦点可控UI

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

时间:2020-11-01 13:02:29

相关推荐

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

前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主要用TV播放器的实现去了解下在智能设备上的开发的相关技术。点击查看原文

转载请说明出处:/sk719887916

通过前两篇的学习,( 安卓Tv开发(二)焦点控制(键盘事件))大家基本了解了安卓事件机制原理,终于间隔三个月后有时间继续完善此系列文章了,下面就开始今天的正题,

本文章将会带大去会实现电视盒子的UI设计,并实现遥控器控制九宫格,并进行翻页效果。

效果:

通过分析此UI,我们可以自定义一个类似grideview的自定义控件,再自定义itemvVew,通过键盘方向键即遥控器反向键控制itemview焦点切换。

一:自定义焦点控制的父布局

1 首先自定义一个可控制焦点的Focusview,

新建一个focusview,继承viewgroup,重写构造方法。充当我们的外部框架,类似流式布局,

public FocusView(Context context) {this(context, null);}public FocusView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FocusView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs);initViewGroup(context);}private void initViewGroup(Context context) {mScroller = new Scroller(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}

写完以上代码有同学可能对scroller的用法,这里就先略微说一下此类 ,Android里Scroller类是为了实现View平滑滚动的一个Helper类。通常在自定义的View时使用,在View中定义一个私有成员mScroller = new Scroller(context)。设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动。

api具体解释如下

mScroller.getCurrX() //获取mScroller当前水平滚动的位置 mScroller.getCurrY() //获取mScroller当前竖直滚动的位置 mScroller.getFinalX() //获取mScroller最终停止的水平位置 mScroller.getFinalY() //获取mScroller最终停止的竖直位置 mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置 mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置 //滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间 mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms mScroller.startScroll(int startX, int startY, int dx, int dy, int duration) puteScrollOffset() //返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在puteScroll()中,用来判断是否滚动是否结束。

mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop()

此处代码为了之后表示滑动的时候,手的移动要大于这个返回的距离值才开始移动控件。

2, 接着 我们重写 onLayout()和onMeasure()方法,今天我们不重点讲解自定义view绘制。

执行测量

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);final int width = MeasureSpec.getSize(widthMeasureSpec);final int height = MeasureSpec.getSize(heightMeasureSpec);mRowHeight = (height - (visibleRows - 1) * mGapHeight - getPaddingTop() - getPaddingBottom()) / visibleRows;mColWidth = (width - (visibleCols - 1) * mGapWidth - getPaddingLeft() - getPaddingRight()) / visibleCols;final int itemCount = mFocusItems.size();for (int i = 0; i < itemCount; i++) {final FocusItem item = mFocusItems.get(i);final View childView = item.getMetroView();final int childWidth = MeasureSpec.makeMeasureSpec((mColWidth + mGapWidth) * item.getColSpan() - mGapWidth, MeasureSpec.EXACTLY);final int childHeight = MeasureSpec.makeMeasureSpec((mRowHeight + mGapHeight) * item.getRowSpan() - mGapHeight, MeasureSpec.EXACTLY);childView.measure(childWidth, childHeight);}scrollTo((mColWidth + mGapWidth) * mCurCol, (mRowHeight + mGapHeight) * mCurRow);}

具体布局:

@Overrideprotected 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 FocusItem item = mFocusItems.get(i);final View childView = item.getMetroView();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);}}}

scrollTo()方法将当前获得焦点的view动画移到指定坐标位置。

转载请说明出处:/sk719887916

等绘制完view,重头戏剧来了,下面我我们就即将重写onTouchEvent()和onInterceptTouchEvent()。

@Overridepublic boolean onTouchEvent(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);final int action = event.getAction();float x = event.getX();float y = event.getY();if (mOrientation == OrientationType.Horizontal)y = 0;else if (mOrientation == OrientationType.Vertical)x = 0;switch (action) {case MotionEvent.ACTION_DOWN:if (!mScroller.isFinished()) {mScroller.abortAnimation();}mLastMotionX = x;mLastMotionY = y;break;case MotionEvent.ACTION_MOVE:int deltaX = (int) (mLastMotionX - x);int deltaY = (int) (mLastMotionY - y);mLastMotionX = x;mLastMotionY = y;scrollBy(deltaX, deltaY);break;case MotionEvent.ACTION_UP:final VelocityTracker velocityTracker = mVelocityTracker;puteCurrentVelocity(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)snapToDestination();else {snapTo(row, col);if (metroListener != null)metroListener.scrollto(row, col);}if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}mTouchState = TOUCH_STATE_REST;break;case MotionEvent.ACTION_CANCEL:mTouchState = TOUCH_STATE_REST;break;}return true;}

ps:VelocityTracker主要用跟踪触摸屏事件的速率,getXVelocity() 或getXVelocity()获得横向和竖向的速率到速率时,使用它们之前必须先调用computeCurrentVelocity(int)来初始化速率的单位 。

mVelocityTracker.addMovement(event);

此方法主要是将Motion event加入到VelocityTracker类实例中.用于控制envent,

mVelocityTracker = VelocityTracker.obtain();

方法来获得VelocityTracker类的一个实例需来跟踪触摸屏事件的速度。

onInterceptTouchEvent()是用于处理事件(重点onInterceptTouchEvent这个事件是从父控件开始往子控件传的,直到有拦截或者到没有这个事件的view,然后就往回从子到父控件,这次是onTouch的)(类似于预处理,当然也可以不处理)并改变事件的传递方向,也就是决定是否允许Touch事件继续向下(子控件)传递,一但返回True(代表事件在当前的viewGroup中会被处理),则向下传递之路被截断(所有子控件将没有机会参与Touch事件),同时把事件传递给当前的控件的onTouchEvent()处理;返回false,则把事件交给子控件的onInterceptTouchEvent(),具体请详见两篇文章对事件的描述。

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {final int action = ev.getAction();if ((action == MotionEvent.ACTION_MOVE)&& (mTouchState != TOUCH_STATE_REST)) {return true;}final float x = ev.getX();switch (action) {case MotionEvent.ACTION_MOVE:final int xDiff = (int) Math.abs(mLastMotionX - x);if (xDiff > mTouchSlop) {mTouchState = TOUCH_STATE_SCROLLING;}break;case MotionEvent.ACTION_DOWN:mLastMotionX = x;mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST: TOUCH_STATE_SCROLLING;break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:mTouchState = TOUCH_STATE_REST;break;}return mTouchState != TOUCH_STATE_REST;}

3 重写以上事件方法,目的就是拦截键盘点击事件,将动画设置获得焦点的view上,这样遥控器按建,焦点就随之移动,我们的动画也随之移动。

接下来继续重写onKeyDown()和onKeyUp()

@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {return super.onKeyDown(keyCode, event);}@Overridepublic boolean onKeyUp(int keyCode, KeyEvent event) {FocusItem focusItem = getFocusMetroItem();if(focusItem == null) {return false;}Log.d(TAG, "in onKeyUp focus.row="+focusItem.getRow()+" focus.col="+focusItem.getCol());int leftCol = mCurCol;int topRow = mCurRow;int rightCol = mCurCol + visibleCols;int buttomRow = mCurRow + visibleRows;Log.d(TAG, "in onKeyUp leftCol="+leftCol+" topRow="+topRow+" rightCol="+rightCol+" buttomRow="+buttomRow);//change pageswitch(keyCode) {case KeyEvent.KEYCODE_DPAD_LEFT:if(mOrientation != OrientationType.Vertical) {if(focusItem.getCol() < leftCol) {scowTo(mCurRow, focusItem.getCol() + focusItem.getColSpan() - visibleCols);}}break;case KeyEvent.KEYCODE_DPAD_RIGHT:if(mOrientation != OrientationType.Vertical) {if(focusItem.getCol() + focusItem.getColSpan() > rightCol) {scowTo(mCurRow, focusItem.getCol());}}break;case KeyEvent.KEYCODE_DPAD_UP:if(mOrientation != OrientationType.Horizontal) {if(focusItem.getRow() < topRow) {scowTo(focusItem.getRow() + focusItem.getRowSpan() - visibleRows, mCurCol);}}break;case KeyEvent.KEYCODE_DPAD_DOWN:if(mOrientation != OrientationType.Horizontal) {if(focusItem.getRow() + focusItem.getRowSpan() > buttomRow) {scowTo(focusItem.getRow(), mCurCol);}}break;}return super.onKeyUp(keyCode, event);}

重写以上事件方法,目的就在键盘弹出的时候,焦点随遥控器上下左后方向键随之移动,动画效果也随之移动。

在上面的重写方法我们可以看到了有scowTo()方法;用Ta我们来控制动画是否需要移动,view是否需要重绘制

public void scowTo(int whichRow, int whichCol) {if (whichRow < 0)whichRow = 0;if (whichCol < 0)whichCol = 0;Log.d(TAG, String.format("snap to row:%d, col:%d", whichRow, whichCol));boolean needRedraw = false;if (mOrientation == OrientationType.Horizontal) {whichRow = 0;if (whichCol + visibleCols > mColsCount)whichCol = Math.max(mColsCount - visibleCols, 0);} else if (mOrientation == OrientationType.Vertical) {whichCol = 0;if (whichRow + visibleRows > mRowsCount)whichRow = Math.max(mRowsCount - visibleRows, 0);} else if (mOrientation == OrientationType.All) {if (whichRow + visibleRows > mRowsCount)whichRow = Math.max(mRowsCount - visibleRows, 0);if (whichCol + visibleCols > mColsCount)whichCol = Math.max(mColsCount - visibleCols, 0);}int deltaX = whichCol * (mColWidth + mGapWidth);int deltaY = whichRow * (mRowHeight + mGapHeight);Log.e(TAG, "end whichRow="+whichRow+" whichCol="+whichCol +" getScrollX()="+getScrollX()+" getScrollY()="+getScrollY());if (getScrollX() != deltaX) {deltaX = deltaX - getScrollX();needRedraw = true;}else {deltaX = 0;}if (getScrollY() != deltaY) {deltaY = deltaY - getScrollY();needRedraw = true;}else {deltaY = 0;}if (needRedraw) {mScroller.startScroll(getScrollX(), getScrollY(), deltaX, deltaY,Math.max(Math.abs(deltaX)/2, Math.abs(deltaY/2)) * 2);mCurRow = whichRow;mCurCol = whichCol;invalidate(); }}

重写了以上方法,我们的view算是能用了,主要通过此自定义view,通过遥控器按键控制itemview是否获得焦点,是否可以滚动,

接下来 我们在加入动画效果,新建一各动画类AnimationFocusManager

二 新建AnimationFocusManager

此类控制翻页以及焦点放大动画效果,

/*** 焦点控制动画控制器.* @author lyk**/public class AnimationMetroManager implements OnFocusChangeListener{int animationIn = -1;int animationOut = -1;boolean animationFocusLock = false;View focusView = null;HashMap<View, OnFocusChangeListener> focusPool = new HashMap<View, View.OnFocusChangeListener>();private Context mContext;public AnimationFocusManager(Context c) {if(c == null) {throw new IllegalArgumentException("the context is null");}mContext = c;}/*** it is Animation is locked ;* set AnimationFocusLocked ,* To facilitate the other animation not to perform* @param lock*/public void setAnimationFocusLock(boolean lock) {boolean oldLock = animationFocusLock;if(oldLock == lock) {return;}animationFocusLock = lock;if(animationFocusLock && animationIn != -1 && focusView != null) {focusView.startAnimation(AnimationUtils.loadAnimation(mContext, animationIn));}else if(!animationFocusLock && animationOut != -1 && focusView != null) {focusView.bringToFront();focusView.startAnimation(AnimationUtils.loadAnimation(mContext, animationOut));}}public void setAnimation(int in, int out) {this.animationIn = in;this.animationOut = out;}public boolean isAvailability() {return !animationFocusLock && animationOut != -1 && animationIn != -1;}public void add(View v, OnFocusChangeListener l) {focusPool.put(v, l);}public void delete(View v) {focusPool.remove(v);}public void clear() {focusPool.clear();focusView = null;}public void onFocusChange(View v, boolean hasFocus) {if(hasFocus) {focusView = v;}if(hasFocus && isAvailability()) {Animation anim = AnimationUtils.loadAnimation(mContext, animationOut);v.bringToFront();v.startAnimation(anim);}else if(isAvailability()){Animation anim = AnimationUtils.loadAnimation(mContext, animationIn);v.startAnimation(anim);}if(focusPool.containsKey(v)) {focusPool.get(v).onFocusChange(v, hasFocus);}}

此类主要是自定义几个方法,设置动画 移,除动画,焦点改变监听等,便于控制焦点,用于view回调。具体不做细说

最后我们不要忘了在自定义的Focusview内初始化此动画

private void initViewGroup(Context context) {mScroller = new Scroller(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();// 加入动画mAnimationFocusController = new AnimationMetroManager(getContext());}

此动画用到的动画xml,移动动画我们可以自己设置自定义动画,不限制demo所示动画。

背景动画

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="/apk/res/android"android:fillAfter="true"android:fillBefore="false"android:shareInterpolator="false" ><scaleandroid:duration="100"android:fromXScale="1.0"android:fromYScale="1.0"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:pivotX="50.0%"android:pivotY="50.0%"android:repeatCount="0"android:toXScale="1.2"android:toYScale="1.2" /></set>

获得焦点放大后的动画

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="/apk/res/android"android:fillAfter="true"android:fillBefore="false"android:shareInterpolator="false" ><scaleandroid:duration="100"android:fromXScale="1.2"android:fromYScale="1.2"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:pivotX="50.0%"android:pivotY="50.0%"android:repeatCount="0"android:toXScale="1.0"android:toYScale="1.0" /></set>

到这一步我们的view已经算是成功了,我们通过自定义view实现了一个类似流式布局的FocusVew,可以通过键盘移动,并能在获得焦点的view的子view的焦点显示动画效果,但里面我们还需要填充itemview(子控件),下面我们将会继续自定义itemView和怎么使用Focusview,具体代码逻辑下篇安卓TV开发(四) 实现主流智能TV视频播放器UI。点击查看原文会继续讲解,

源码地址:/NeglectedByBoss/FocusVIew



本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。