天天看點

android tv焦點放大特效實作及RecyclerView使用

如下是tv焦點放大大緻分析,具體實作已優化,請檢視demo:https://github.com/lewic1987/tvdemo.git

Android TV上的焦點凸顯特效相信大家都看到過,那麼我們就來實作它吧,首先上張效果圖。

android tv焦點放大特效實作及RecyclerView使用
android tv焦點放大特效實作及RecyclerView使用

先說一下實作原理,主要通過重寫RelativeLayout實作item,之後在其中加入scalanimation動畫效果。剛開始處理時,還是發現了一些問題,比如item放大後會被其他item遮擋,如何添加選中邊框等等,以及動畫的實作等等。下面放上實作細節。

首先是item的代碼:

[html]  view plain copy

  1. <view xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:id="@+id/item"  
  3.     android:layout_width="@dimen/home_channel_item_width"  
  4.     android:layout_height="@dimen/home_channel_item_height"  
  5.     class="com.eastelsoft.tv.widget.home.HomeItemContainer"  
  6.     android:clickable="true"  
  7.     android:focusable="true"  
  8.     android:focusableInTouchMode="true"  
  9.     android:clipChildren="false"  
  10.     android:clipToPadding="false" >  
  11.     <com.eastelsoft.tv.widget.ESImageView  
  12.         android:id="@+id/img"  
  13.         android:layout_width="fill_parent"  
  14.         android:layout_height="fill_parent"  
  15.         android:background="@drawable/holder_nor"  
  16.         android:duplicateParentState="true"  
  17.         android:scaleType="fitXY" />  
  18.     <!-- -->  
  19.     <com.eastelsoft.tv.widget.ESImageView  
  20.         android:id="@+id/hover"  
  21.         android:layout_width="fill_parent"  
  22.         android:layout_height="fill_parent"  
  23.         android:contentDescription="@string/desc"  
  24.         android:duplicateParentState="true"  
  25.         android:scaleType="fitXY"  
  26.         android:src="@drawable/sl_image_home_navigator" />  
  27.     <TextView  
  28.         android:id="@+id/text"  
  29.         android:layout_width="fill_parent"  
  30.         android:layout_height="wrap_content"  
  31.         android:layout_alignParentBottom="true"  
  32.         android:layout_marginBottom="@dimen/home_item_text_margin"  
  33.         android:layout_marginLeft="@dimen/home_item_text_margin"  
  34.         android:layout_marginRight="@dimen/home_item_text_margin"  
  35.         android:ellipsize="marquee"  
  36.         android:gravity="bottom|right|center"  
  37.         android:includeFontPadding="false"  
  38.         android:marqueeRepeatLimit="5"  
  39.         android:maxWidth="@dimen/px310"  
  40.         android:shadowColor="#88333333"  
  41.         android:shadowDx="2.0"  
  42.         android:shadowDy="2.0"  
  43.         android:shadowRadius="2.0"  
  44.         android:singleLine="true"  
  45.         android:textColor="#ffffffff" />  
  46. </view>  

這裡定義了一個自定義view,代碼在後面放上,每個item裡添加了一個img,用于放置内容圖檔,一個hover,用于顯示選中的邊框,以及一個text,顯示一些文字說明。

hover的src是一個selector drawable,當未focus時,它的背景是tansparent,當focus,放入外框圖檔。

自定義的HomeItemContainer 代碼:

[java]  view plain copy

  1. public class HomeItemContainer extends RelativeLayout {  
  2.     private Rect mBound;  
  3.     private Drawable mDrawable;  
  4.     private Rect mRect;  
  5.     private Animation scaleSmallAnimation;  
  6.     private Animation scaleBigAnimation;  
  7.     public HomeItemContainer(Context context) {  
  8.         super(context);  
  9.         init();  
  10.     }  
  11.     public HomeItemContainer(Context context, AttributeSet attrs, int defStyle) {  
  12.         super(context, attrs, defStyle);  
  13.         init();  
  14.     }  
  15.     public HomeItemContainer(Context context, AttributeSet attrs) {  
  16.         super(context, attrs);  
  17.         init();  
  18.     }  
  19.     protected void init() {  
  20.         setWillNotDraw(false);  
  21.         mRect = new Rect();  
  22.         mBound = new Rect();  
  23.         mDrawable = getResources().getDrawable(R.drawable.poster_shadow_4);//nav_focused_2,poster_shadow_4  
  24.         setChildrenDrawingOrderEnabled(true);  
  25.     }  
  26.     @Override  
  27.     protected void onAttachedToWindow() {  
  28.         super.onAttachedToWindow();  
  29.     }  
  30.     @Override  
  31.     public void draw(Canvas canvas) {  
  32.         super.draw(canvas);  
  33.     }  
  34.     @Override  
  35.     protected void onDraw(Canvas canvas) {  
  36.         if (hasFocus()) {  
  37.             System.out.println("HomeItemContainer focus : true ");  
  38.             super.getDrawingRect(mRect);  
  39.             mBound.set(-39+mRect.left, -39+mRect.top, 39+mRect.right, 39+mRect.bottom);  
  40.             mDrawable.setBounds(mBound);  
  41.             canvas.save();  
  42.             mDrawable.draw(canvas);  
  43.             canvas.restore();  
  44.         }  
  45.         super.onDraw(canvas);  
  46.     }  
  47.     @Override  
  48.     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {  
  49.         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);  
  50.         if (gainFocus) {  
  51.             bringToFront();  
  52.             getRootView().requestLayout();  
  53.             getRootView().invalidate();  
  54.             zoomOut();  
  55.         } else {  
  56.             zoomIn();  
  57.         }  
  58.     }  
  59.     private void zoomIn() {  
  60.         if (scaleSmallAnimation == null) {  
  61.             scaleSmallAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.anim_scale_small);  
  62.         }  
  63.         startAnimation(scaleSmallAnimation);  
  64.     }  
  65.     private void zoomOut() {  
  66.         if (scaleBigAnimation == null) {  
  67.             scaleBigAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.anim_scale_big);  
  68.         }  
  69.         startAnimation(scaleBigAnimation);  
  70.     }  
  71. }  

注意onFocusChanged方法,為防止item被其他item遮擋,先調用bringToFront方法,使此item處于最上層,之後調用父view的方法進行重新繪制,其實注意一點,item必須處于同一父view中,否則requestLayout和invalidate可能會不起作用,隻适用于RelativeLayout布局,經測試LinearLayout不适用。

順便放上一個scaleanimation縮小的效果代碼:

[html]  view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <set xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:fillAfter="false"  
  4.     android:fillBefore="true"  
  5.     android:shareInterpolator="false" >  
  6.     <scale  
  7.         android:duration="200"  
  8.         android:fromXScale="1.1"  
  9.         android:fromYScale="1.1"  
  10.         android:interpolator="@android:anim/accelerate_decelerate_interpolator"  
  11.         android:pivotX="50.0%"  
  12.         android:pivotY="50.0%"  
  13.         android:repeatCount="0"  
  14.         android:toXScale="1.0"  
  15.         android:toYScale="1.0" />  
  16. </set>  

裡面的屬性就不詳細介紹了,有興趣的可以自己谷歌。

最後放上item的父view:

[html]  view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="horizontal"  
  6.     android:padding="10dp"  
  7.     android:clipChildren="false"  
  8.     android:clipToPadding="false" >  
  9.     <include   
  10.         android:id="@+id/channel_0"  
  11.         android:layout_width="@dimen/home_channel_item_width"  
  12.         android:layout_height="@dimen/home_channel_item_height"  
  13.         layout="@layout/home_page_channel_item"  
  14.         android:layout_alignParentLeft="true"  
  15.         android:layout_alignParentTop="true"  
  16.         android:layout_margin="3dp" />  
  17.     <include  
  18.         android:id="@+id/channel_1"  
  19.         android:layout_width="@dimen/home_channel_item_width"  
  20.         android:layout_height="@dimen/home_channel_item_height"  
  21.         layout="@layout/home_page_channel_item"  
  22.         android:layout_below="@id/channel_0"  
  23.         android:layout_alignLeft="@id/channel_0" />  
  24.     <include   
  25.         android:id="@+id/channel_2"  
  26.         android:layout_width="@dimen/home_channel_item_width"  
  27.         android:layout_height="@dimen/home_channel_item_height"  
  28.         layout="@layout/home_page_channel_item"  
  29.         android:layout_toRightOf="@id/channel_0"  
  30.         android:layout_alignTop="@id/channel_0"  
  31.         android:layout_marginRight="3dp"  
  32.         android:layout_marginBottom="3dp"/>  
  33.     <include   
  34.         android:id="@+id/channel_3"  
  35.         android:layout_width="@dimen/home_channel_item_width"  
  36.         android:layout_height="@dimen/home_channel_item_height"  
  37.         layout="@layout/home_page_channel_item"  
  38.         android:layout_alignLeft="@id/channel_2"  
  39.         android:layout_below="@id/channel_2"/>  
  40.     <include   
  41.         android:id="@+id/channel_4"  
  42.         android:layout_width="@dimen/home_channel_item_width"  
  43.         android:layout_height="@dimen/home_channel_item_height"  
  44.         layout="@layout/home_page_channel_item"  
  45.         android:layout_toRightOf="@id/channel_2"  
  46.         android:layout_alignTop="@id/channel_2"  
  47.         android:layout_marginRight="3dp"  
  48.         android:layout_marginBottom="3dp"/>  
  49.     <include   
  50.         android:id="@+id/channel_5"  
  51.         android:layout_width="@dimen/home_channel_item_width"  
  52.         android:layout_height="@dimen/home_channel_item_height"  
  53.         layout="@layout/home_page_channel_item"  
  54.         android:layout_alignLeft="@id/channel_4"  
  55.         android:layout_below="@id/channel_4"/>  
  56.     <include   
  57.         android:id="@+id/channel_6"  
  58.         android:layout_width="@dimen/home_channel_item_width"  
  59.         android:layout_height="@dimen/home_channel_item_height"  
  60.         layout="@layout/home_page_channel_item"  
  61.         android:layout_toRightOf="@id/channel_4"  
  62.         android:layout_alignTop="@id/channel_4"  
  63.         android:layout_marginRight="3dp"  
  64.         android:layout_marginBottom="3dp"/>  
  65.     <include   
  66.         android:id="@+id/channel_7"  
  67.         android:layout_width="@dimen/home_channel_item_width"  
  68.         android:layout_height="@dimen/home_channel_item_height"  
  69.         layout="@layout/home_page_channel_item"  
  70.         android:layout_alignLeft="@id/channel_6"  
  71.         android:layout_below="@id/channel_6"/>  
  72.     <include   
  73.         android:id="@+id/channel_8"  
  74.         android:layout_width="@dimen/home_channel_item_width"  
  75.         android:layout_height="@dimen/home_channel_item_height"  
  76.         layout="@layout/home_page_channel_item"  
  77.         android:layout_toRightOf="@id/channel_6"  
  78.         android:layout_alignTop="@id/channel_6"  
  79.         android:layout_marginRight="3dp"  
  80.         android:layout_marginBottom="3dp"/>  
  81.     <include   
  82.         android:id="@+id/channel_9"  
  83.         android:layout_width="@dimen/home_channel_item_width"  
  84.         android:layout_height="@dimen/home_channel_item_height"  
  85.         layout="@layout/home_page_channel_item"  
  86.         android:layout_alignLeft="@id/channel_8"  
  87.         android:layout_below="@id/channel_8"/>  
  88. </RelativeLayout>  

這裡我定義了10個item,注意RelativeLayout的兩個屬性,clipChildren設定false,讓children view可以超出自身所設定的大小,clipToPadding設定為false,讓children view可以使用padding 的位置進行繪制,有了這2個屬性,item就可以實作放大而不被遮擋了。

好了,焦點特效的教程就說到這裡了,有問題可以在評論中回報。