项目感悟:TV端项目的0—1

来源:互联网 发布:诉苦大会 知乎 编辑:程序博客网 时间:2024/05/21 22:49

0—1之外:

对于TV端的Android项目,其实并没有想象中的那么神奇,与普通的Android应用一样,全部都是Android自己的东西,只是在TV端,还需要考虑的问题之一就是焦点移动问题(我觉得这是制约TV端app发展的一个最主要的问题),怎样在使用上下左右,确定,返回,菜单键来控制整个应用的操作。假如,我们能像操作手机那样操作电视,而且在流畅程度上与手机并无差异的话,那该是多么美好的一件事情。试想一下,如果有个可以模拟电视界面的东西来让用户点击,这个场面将会很美。说了这么多其他的东西,我们来说说项目的问题。一个项目从无到有其实需要经历很多东西,但是作为一个开发人员来说,我们需要关心的最为主要的就是怎么更好的开发这款产品。

0 过程:

在开发这个项目的时候,我们首先要确定,做什么,怎么做。当然,对于开发人员来说,做什么已经不要我们考虑了,我们要完成的事情就是考虑怎么做。由于以前有做过和这个项目类似的东西,所以在整体的框架上面,我们就延用了上一个项目的东西,一来,省去了很多事情,二来,规避掉了很多不必要的风险。

总结一下我们在项目中用到的主要的东西:(要我自己整理他们的用法,可能还有一定的难度,可能附上大神们的网址来的更快,更好一些)

1、EventBus:点击打开链接

2、Gson:点击打开链接

3、Fragment:点击打开链接

4、ScaleRelativeLayout:是一个TV端经常用到的东西-选中放大,比起不同的选中操作,来的更加有灵动性;

5、RoundedImageView:用于对于图片切圆角很有用,比起Android自己切圆角方法要圆润的多;

6、Glide:一个优质的图片加载工具

7、RecycleView:点击打开链接

8、MediaPlayer+SurfaceView:用于对视频的播放。

以上几个东西就是我们在项目中主要用的到的一些东西,使用现成的东西固然方便,但在使用之后,要对它进行分析和理解,毕竟只有真正理解之后才会有更好的使用结果产生。确定完怎么来做这个东西之后,下面剩下的问题就是合作开发了,这个就是从0到1的一个过程,这个过程就不用再仔细描述了把,相信每一个程序员们对这个过程都再熟悉不过了。

1 过程:

当项目成型之后,在这个过程中需要弄很多的东西,包括项目中出现的bug问题,项目中实现方式不合理的地方,项目中考虑问题不全面的地方,都会这这里体现出来。虽然说这里面有的问题应该在项目开发过程中,或者是项目开始开发之前就应该想得到,但是这是在拥有丰富经验的前提条件之下才可能的,并且这个并不是完全可以规避的问题,如果能全部都考虑完全了,那么所有的项目都只有一次的开发周期。在这个过程中,或多或少会遇到改需求的问题,对于这个问题,我们要辩证的看待。对于我们来说,本来实现完成就是一件很付出心血的事情,但是突然不要,心理或多或少会有一些想法,但是考虑到能有更好的用户体验,这个问题还是可以放在一边的。同时在这个过程中,可以更好的优化项目中的一些东西,比如项目中的一些流畅性问题,比如一些更合理的用户操作方式,再比如一些酷炫的效果实现,都可以在加入到这个项目中去。我感觉在这个阶段的好坏,会关系到这个产品的好坏。

0—1 之间:

总结完成之后,我们就来开讨论一下在项目中遇到的一些问题,和分享一些项目中用的一些方法。

1、Glide固然是一种好用的图片加载工具,它的简单用法,和他的一些效果都是那么的让人喜欢,在项目中,我们就遇到了一个这样的问题,在每次首次加载一组图片时,都会出现加载不出来,只显示默认图的情况,当再次切回来的时候,图片又能很好地显示出来,导致这个问题的主要原因是我们使用了重载过的ImageView,就会引起这样的问题,当然我们后面使用的解决办法是这样。

<pre name="code" class="java"><span style="font-size:14px;">SimpleTarget simpleTarget = new SimpleTarget<Bitmap>() {</span>
@Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { imageView.setImageBitmap(resource); } };Glide.with(context.getApplicationContext()) .load(imageUrl) .asBitmap() .fitCenter() .placeholder(R.drawable.default_bg_big) .error(R.drawable.default_bg_big)
.priority(Priority.HIGH) .into(simpleTarget);

这样就能很好地解决这个问题,当然也牺牲掉了一些别的东西,比如说加载时的动效。起初还考虑将每一张默认图都设置成gif的形式,但是考虑到性能的问题就放弃了。

2、RecycleView获取第一个item的焦点,或者是获取其中某一个位置的焦点:

对于刚刚初始化好的一个RecycleView来说要让它的第一个item获取焦点其实很简单就使用这样的一个方式就可以:

<span style="font-size:14px;">recyclerViewList.getChildAt(0).requestFocus();</span>

但是这样的方式会产生一个NullPointerException,造成的原因是在初始化好了的时候,RecycleView里面还有没有完全准备好来获得他的第一个item焦点,这样就需要使用Handler来做一个延时操作来解决这个问题,虽然说这个解决方法有点牵强,但是也是一个好办法,不是吗。在获取RecycleView中的其中某一个item的焦点时,会有这样的问题,RecycleView的初始化方式是只显示屏幕中能看到的item,每次item在屏幕中的位置是固定不变的,比如说我们要回去100item中的第50个焦点的话,就会有一定的困难,后来我们找到了这个问题的解决办法:

        handler.postDelayed(new Runnable() {            @Override            public void run() {                linearLayoutManager.scrollToPositionWithOffset(currentPosition, 0);            }        }, 200);        handler.postDelayed(new Runnable() {            @Override            public void run() {                RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(currentPosition);                ((VideoPlayerAdapter.MyHolder) viewHolder).layout.requestFocus();            }        }, 300);
这样就能完美的解决上面说的那个问题。

3、在使用MeidaPlayer的时候,除了遵循它的生命周期,还要注意的一个事情就是,在Player reset或者是release的时候,这两个是耗时操作,如果它们将他们两个放在主线程里面来处理的话,可能会照成页面卡顿,最有效的方法是(对就是你想的那样)将他们放在线程里面去处理。

4、焦点问题一直是TV端的一个重要问题,在项目中,我们遇到了焦点乱跳的问题,这个问题造成的原因有几个,可能是数据重新加载,导致界面发生变化,焦点就会移动到最近的位置,或者是你想不到的地方;也有可能是一边的焦点已经移动到最后了,由于就近原则,它会移动到别的地方去,等等一些原因会造成焦点乱跳的问题,所以在处理这些问题时,要合理的采用不同的方法来解决,屏蔽掉它的案件事件,比如说在某种状态下,希望他不响应下键,或者是其他件,就在onKeyDown 那个里面返回一个true就搞定了,或者是使用的setNextFucus...()方法来设置,或者是其他的一些办法来解决,只要用心想,总会找到解决的办法的。

5、由于放大的原因,会造成itemVIew被遮挡的问题,这个问题其实很好解,比如说由于padding造成的原因,我们可以在它的父类view中设置这两个属性就解决了:

  android:clipChildren="false"  android:clipToPadding="false"
或者是使用view里面的一个方法来手动设置一个变量来把它调整到显示的最顶端:

    @Override    protected int getChildDrawingOrder(int childCount, int i) {        // TODO Auto-generated method stub        if(pos<0){            return i;        }        else{            if(i==childCount -1){                if(pos>i)                    pos=i;                return pos;            }            if(i==pos){                return childCount -1;            }        }        return i;    }

6、在使用滚动的时候,如果想让一个滚动的效果更加圆润的话可以使用这样的方法:

  ValueAnimator animator = ValueAnimator.ofFloat(1, 0f);        animator.setDuration(400);        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                float f = (Float) animation.getAnimatedValue();                horizontalScrollView.smoothScrollTo((int) (x*f),0);            }        });        animator.addListener(new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animation) {            }            @Override            public void onAnimationEnd(Animator animation) {                isScrollEnd = false;                isScrollStart = true;            }            @Override            public void onAnimationCancel(Animator animation) {            }            @Override            public void onAnimationRepeat(Animator animation) {            }        });        animator.start();

7、然后在分享一个RecycleView居中的类,它能保证我们在RecycleView中焦点移动时,除了边界上的几个其他的都是保持在中间的位置上:

public class AppRecyclerView extends RecyclerView {private int mTopPosition = -1;public static final int CHANGE_TYPE_PAGE = 0;public static final int CHANGE_TYPE_CENTER = 1;public static final int CHANGE_TYPE_NONE = 3;private int mChangeType = CHANGE_TYPE_NONE;private static final int DEFAULT_EDGE_OFFSET = 60;private int mEdgeOffset = DEFAULT_EDGE_OFFSET;public AppRecyclerView(Context arg0) {super(arg0);setChildrenDrawingOrderEnabled(true);}public AppRecyclerView(Context arg0, AttributeSet arg1) {super(arg0, arg1);setChildrenDrawingOrderEnabled(true);TypedArray a= getContext().obtainStyledAttributes(arg1, R.styleable.appRecyclerView);mChangeType=a.getInt(R.styleable.appRecyclerView_changeType,CHANGE_TYPE_NONE);Log.i("mChangeType",mChangeType+"");}public AppRecyclerView(Context arg0, AttributeSet arg1, int arg2) {super(arg0, arg1, arg2);setChildrenDrawingOrderEnabled(true);}/** * 设置切换效果,默认为切页。<br/> * 切页:CHANGE_TYPE_PAGE<br/> * 居中:CHANGE_TYPE_CENTER<br/> * 原生:CHANGE_TYPE_NONE *  * @param tType */public void setChangeType(int tType) {mChangeType = tType;}@Overrideprotected int getChildDrawingOrder(int childCount, int i) {if (i == 0) {mTopPosition = -1; // 重绘时使标记位失效}View tView = getChildAt(i);if (tView != null) {if (tView.isFocused()) { // 焦点位置最后绘制,保持在顶层mTopPosition = i;return childCount - 1;} else {if (i == childCount - 1 && mTopPosition != -1) { // 最后一个位置与焦点位置交替return mTopPosition;}}}return i;}@Overridepublic boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {switch (mChangeType) {case CHANGE_TYPE_PAGE:return requestVerticalPageChange(child, rect);case CHANGE_TYPE_CENTER:return requestHorizontalCenterChange(child, rect);case CHANGE_TYPE_NONE:return requestVerticalCenterChange(child, rect);default:return super.requestChildRectangleOnScreen(child, rect, immediate);}}/** * 设置边缘的差分滚动距离,解决在边缘处按键无法调到下一个位置导致的焦点不可控问题;<br/> * 建议设置略大于item之间的间隔值, 默认值为60px<br/> * @param tOffset */public void setEdgeOffset(int tOffset){mEdgeOffset = tOffset;}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if (event.getAction() == KeyEvent.ACTION_DOWN) {//if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP || event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN){//return true;//}View tView = getFocusedChild();if (tView != null) {switch (event.getKeyCode()) {case KeyEvent.KEYCODE_DPAD_UP:if (FocusFinder.getInstance().findNextFocus(this, tView, View.FOCUS_UP) == null) {int tPosition = getChildAdapterPosition(tView);if (tPosition != 0) {scrollBy(0,-mEdgeOffset);if (event.getRepeatCount() > 1){return true;}}}break;case KeyEvent.KEYCODE_DPAD_DOWN:if (FocusFinder.getInstance().findNextFocus(this, tView, View.FOCUS_DOWN) == null) {int tPosition = getChildAdapterPosition(tView);if (tPosition != getAdapter().getItemCount() - 1) {scrollBy(0,mEdgeOffset);if (event.getRepeatCount() > 1){return true;}}}break;default:break;}}}return super.dispatchKeyEvent(event);}/** * 水平切页效果 *  * @param child * @param rect * @return */private boolean requestHorizontalPageChange(View child, Rect rect) {final int parentLeft = getPaddingLeft();final int parentRight = getWidth() - getPaddingRight();final int childLeft = child.getLeft() + rect.left - child.getScrollX();final int childRight = childLeft + rect.width();final int offScreenLeft = Math.min(0, childLeft - parentLeft);final int offScreenRight = Math.max(0, childRight - parentRight);// Favor the "start" layout direction over the end when bringing one// side or the other// of a large rect into view. If we decide to bring in end because start// is already// visible, limit the scroll such that start won't go out of bounds.int dx;if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {// 右到左的局部dx = offScreenRight != 0 ? offScreenRight : Math.max(offScreenLeft, childRight - parentRight);} else {dx = offScreenLeft != 0 ? offScreenLeft : Math.min(childLeft - parentLeft, offScreenRight);}if (dx != 0) {if (dx > 0) {dx = dx + getWidth() - child.getWidth() - child.getWidth() / 2;} else {dx = dx - getWidth() + child.getWidth() + child.getWidth() / 2;}this.smoothScrollBy(dx, 0);return true;}return false;}private boolean requestVerticalPageChange(View child, Rect rect) {final int parentTop = getPaddingTop();final int parentBottom = getHeight() - getPaddingBottom();final int childTop = child.getTop() + rect.top - child.getScrollY();final int childBottom = childTop + rect.height();final int offScreenTop = Math.min(0, childTop - parentTop);final int offScreenBottom = Math.max(0, childBottom - parentBottom);// Favor the "start" layout direction over the end when bringing one// side or the other// of a large rect into view. If we decide to bring in end because start// is already// visible, limit the scroll such that start won't go out of bounds.int dy;if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {// 右到左的局部dy = offScreenBottom != 0 ? offScreenBottom : Math.max(offScreenTop, childBottom - parentBottom);} else {dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom);}if (dy != 0) {if (dy > 0) {dy = dy + getHeight() - child.getHeight() - child.getHeight() / 2;} else {dy = dy - getHeight() + child.getHeight() + child.getHeight() / 2;}this.smoothScrollBy(0, dy);return true;}return false;}/** * 竖直居中效果 *  * @param child * @param rect * @return */private boolean requestVerticalCenterChange(View child, Rect rect) {final int parentTop = getPaddingTop();final int parentBottom = getHeight() - getPaddingBottom();final int childTop = child.getTop() + rect.top - child.getScrollY();final int childBottom = childTop + rect.height();int dy = 0;final int offScreenTop = childTop - (parentTop + getHeight() / 2 - child.getHeight() / 2);final int offScreenBottom = childBottom - (parentBottom + getHeight() / 2 - child.getHeight() / 2);if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { // 右到左的局部dy = offScreenBottom;} else {dy = offScreenTop;}if (dy != 0) {this.smoothScrollBy(0, dy);return true;}return false;}/** * 水平居中效果 * * @param child * @param rect * @return */private boolean requestHorizontalCenterChange(View child, Rect rect) {final int parentLeft = getPaddingLeft();final int parentRight = getWidth() - getPaddingRight();final int childLeft = child.getLeft() + rect.left - child.getScrollX();final int childRight = childLeft + rect.width();int dx = 0;final int offScreenLeft = childLeft - (parentLeft + getWidth() / 2 - child.getWidth() / 2);final int offScreenRight = childRight - (parentRight + getWidth() / 2 - child.getWidth() / 2);if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { // 右到左的局部dx = offScreenRight;} else {dx = offScreenLeft;}if (dx != 0) {this.smoothScrollBy(dx, 0);return true;}return false;}}
item选中放大的ScaleRelativeLayout :

public class ScaleRelativeLayout extends RelativeLayout implements SpringListener {private Spring mSpring;private float SPRING_MIN_VALUE = 1;private float SPRING_MAX_VALUE = 1.1f;public ScaleRelativeLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public void setSPRING_MAX_VALUE(float SPRING_MAX_VALUE) {this.SPRING_MAX_VALUE = SPRING_MAX_VALUE;}public ScaleRelativeLayout(Context context, AttributeSet attrs) {super(context, attrs);}public ScaleRelativeLayout(Context context) {super(context);}private void render() {float val = (float) mSpring.getCurrentValue();this.setScaleX(val);this.setScaleY(val);}public void scaleOut() {if (null == mSpring) {mSpring = SpringSystem.create().createSpring().addListener(this).setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(100, 6));mSpring.setCurrentValue(SPRING_MIN_VALUE);}mSpring.setEndValue(SPRING_MAX_VALUE);}public void scaleIn() {if (null == mSpring) {mSpring = SpringSystem.create().createSpring().addListener(this).setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(100, 6));}mSpring.setEndValue(SPRING_MIN_VALUE);}@Overridepublic void onSpringActivate(Spring arg0) {this.setLayerType(View.LAYER_TYPE_HARDWARE, null);}@Overridepublic void onSpringAtRest(Spring arg0) {this.setLayerType(View.LAYER_TYPE_NONE, null);}@Overridepublic void onSpringEndStateChange(Spring arg0) {}@Overridepublic void onSpringUpdate(Spring arg0) {render();}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();}}

最后,再给自己提一点小目标——写出干净整洁的代码。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 来不及开ei检索证明怎么办 淘宝生产许可编号一定要填怎么办 官网下单被砍单怎么办美卡美私 大学网络课程挂了怎么办 如果二审超过6个月还不判怎么办? sci的proof时间超了怎么办 合肥电大考试没过怎么办 社保账号密码忘记了怎么办 住房公积金账号密码忘记了怎么办 燃气费单子丢了怎么办 商标初审公告期内被异议怎么办 手被山药痒了怎么办 9个月宝宝不吃奶粉怎么办 八个月宝宝拉粑粑费劲怎么办? 两个月小孩不吃奶粉怎么办 两个月的小孩不吃奶粉怎么办 两个多月宝宝不吃奶怎么办 三个多月宝宝不爱吃奶怎么办 4个月宝宝不吃奶怎么办 5个月宝宝不爱吃奶怎么办 九个月宝宝一直流鼻涕怎么办 九个月宝宝一直咳嗽怎么办 宝宝3岁不爱喝水怎么办 1岁宝宝不肯喝水怎么办 三个月宝宝体检说严重缺钙怎么办 1岁半宝宝不吃药怎么办 1岁宝宝抗拒吃药怎么办 六个月宝宝不爱吃辅食怎么办 宝宝九个月了不爱吃辅食怎么办 八个月宝宝不喜欢吃辅食怎么办 小孩米粉吃多了怎么办 宝宝四个月了奶水不足怎么办 4个月奶水不足怎么办 孩子不吃奶粉母乳又不够怎么办 宝宝吃母乳上火了怎么办 5个月宝宝厌奶期怎么办 九个月宝宝不吃奶粉怎么办 第5个月奶不够吃怎么办 九个月的宝宝不吃奶粉怎么办 9个月宝宝不肯吃怎么办 11个月不吃辅食怎么办