├── README.md
├── TvNavigationLayout-java
├── .gitignore
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── hxxdashai
│ │ │ └── android
│ │ │ └── navigation
│ │ │ └── ApplicationTest.java
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── hxxdashai
│ │ │ └── android
│ │ │ └── navigation
│ │ │ ├── MainActivity.java
│ │ │ ├── util
│ │ │ └── SoundUtil.java
│ │ │ └── widget
│ │ │ ├── NavigationCursorView.java
│ │ │ └── NavigationLinearLayout.java
│ │ └── res
│ │ ├── drawable
│ │ └── selector_text_bg.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-xxhdpi
│ │ ├── bg_main.jpg
│ │ ├── ic_launcher.png
│ │ └── ic_navigation.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── strings.xml
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
└── settings.gradle
├── TvNavigationLayout-kotlin
├── .gitignore
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── hxxdashai
│ │ │ └── android
│ │ │ └── navigation
│ │ │ └── ApplicationTest.java
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── hxxdashai
│ │ │ └── android
│ │ │ └── navigation
│ │ │ ├── MainActivity.kt
│ │ │ ├── util
│ │ │ └── SoundUtil.kt
│ │ │ └── widget
│ │ │ ├── NavigationCursorView.kt
│ │ │ └── NavigationLinearLayout.kt
│ │ └── res
│ │ ├── drawable
│ │ └── selector_text_bg.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-xxhdpi
│ │ ├── bg_main.jpg
│ │ ├── ic_launcher.png
│ │ └── ic_navigation.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── strings.xml
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
└── settings.gradle
└── gif
└── gif1.gif
/README.md:
--------------------------------------------------------------------------------
1 | # TvNavigationLayout
2 |
3 | 代码对应有java和kotlin版本,编译后可直接运行。
4 |
5 | 此项目是一个用于TV端的导航栏,具体介绍请点击:
6 | [项目解说地址](https://www.jianshu.com/p/cf818a09f756)
7 |
8 |
9 |
10 | 何不star一个再走~
11 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "com.hxxdashai.android.navigation"
7 | minSdkVersion 23
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 | }
19 |
20 | dependencies {
21 | implementation 'com.android.support:support-v4:26.1.0'
22 | }
23 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/androidTest/java/com/hxxdashai/android/navigation/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/java/com/hxxdashai/android/navigation/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.KeyEvent;
6 | import android.view.View;
7 | import android.widget.TextView;
8 |
9 | import com.hxxdashai.android.navigation.widget.NavigationCursorView;
10 | import com.hxxdashai.android.navigation.widget.NavigationLinearLayout;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * Created by Mr.T on 2018/4/25.
17 | */
18 |
19 | public class MainActivity extends Activity {
20 |
21 | private NavigationLinearLayout mNavigationLinearLayout;
22 | private NavigationCursorView mNavigationCursorView;
23 | private TextView mContent;
24 |
25 | protected void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_main);
28 | mNavigationLinearLayout = (NavigationLinearLayout) findViewById(R.id.mNavigationLinearLayout_id);
29 | mNavigationCursorView = (NavigationCursorView) findViewById(R.id.mNavigationCursorView_id);
30 | mContent = (TextView) findViewById(R.id.mContent);
31 | List data = new ArrayList<>();
32 | data.add("我的电视");
33 | data.add("影视");
34 | data.add("教育");
35 | data.add("游戏");
36 | data.add("应用");
37 | data.add("动漫");
38 | data.add("少儿");
39 | data.add("VIP专区");
40 | mNavigationLinearLayout.setDataList(data);
41 | mNavigationLinearLayout.setNavigationListener(mNavigationListener);
42 | mNavigationLinearLayout.setNavigationCursorView(mNavigationCursorView);
43 | mNavigationLinearLayout.requestFocus();
44 | mContent.setOnKeyListener(new View.OnKeyListener() {
45 | @Override
46 | public boolean onKey(View v, int keyCode, KeyEvent event) {
47 | if (event.getAction() == KeyEvent.ACTION_DOWN)
48 | switch (keyCode) {
49 | case KeyEvent.KEYCODE_DPAD_LEFT:
50 | case KeyEvent.KEYCODE_DPAD_RIGHT:
51 | mNavigationLinearLayout.jumpTo(keyCode);
52 | }
53 | return false;
54 | }
55 | });
56 | }
57 |
58 | private NavigationLinearLayout.NavigationListener mNavigationListener = new NavigationLinearLayout.NavigationListener() {
59 | @Override
60 | public void onNavigationChange(int pos, int keyCode) {
61 | switch (keyCode) {
62 | case KeyEvent.KEYCODE_DPAD_LEFT:
63 | case KeyEvent.KEYCODE_DPAD_RIGHT: //模拟刷新内容区域
64 | mContent.setText("我是内容显示区域,当前页面为:" + pos + ",左右切换内容,上键回到导航栏");
65 | break;
66 | case KeyEvent.KEYCODE_DPAD_UP:
67 | case KeyEvent.KEYCODE_DPAD_DOWN:
68 | break;
69 | case KeyEvent.KEYCODE_MENU://模拟重新编辑刷新了导航栏栏目数据
70 | List data = new ArrayList<>();
71 | data.add("分栏1");
72 | data.add("分栏2");
73 | data.add("分栏3");
74 | data.add("分栏4");
75 | data.add("分栏5");
76 | data.add("分栏6");
77 | data.add("分栏7");
78 | data.add("分栏8");
79 | mNavigationLinearLayout.setDataList(data);
80 | break;
81 | }
82 | }
83 | };
84 | }
85 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/java/com/hxxdashai/android/navigation/util/SoundUtil.java:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation.util;
2 |
3 | import android.content.Context;
4 | import android.media.AudioManager;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by Mr.T on 2018/4/25.
9 | */
10 |
11 | public class SoundUtil {
12 |
13 | public static void playClickSound(View view) {
14 | if (view.isSoundEffectsEnabled()) {
15 | AudioManager manager = (AudioManager) view.getContext().getSystemService(Context.AUDIO_SERVICE);
16 | manager.playSoundEffect(AudioManager.FX_KEY_CLICK);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/java/com/hxxdashai/android/navigation/widget/NavigationCursorView.java:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation.widget;
2 |
3 | import android.animation.AnimatorSet;
4 | import android.animation.ObjectAnimator;
5 | import android.content.Context;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.widget.ImageView;
9 |
10 | /**
11 | * Created by Mr.T on 2018/4/25.
12 | */
13 |
14 | public class NavigationCursorView extends ImageView {
15 |
16 | private long mDuration = 0L;
17 | private int mLastLocation = 0;
18 | private final AnimatorSet set = new AnimatorSet();
19 |
20 | public NavigationCursorView(Context context) {
21 | this(context, null);
22 | }
23 |
24 | public NavigationCursorView(Context context, AttributeSet attrs) {
25 | this(context, attrs, 0);
26 | }
27 |
28 | public NavigationCursorView(Context context, AttributeSet attrs, int defStyleAttr) {
29 | super(context, attrs, defStyleAttr);
30 | setVisibility(View.INVISIBLE);
31 | }
32 |
33 | public final void fsatJumpTo(int location) {
34 | mDuration = 1L;
35 | jumpTo(location);
36 | mDuration = 200L;
37 | }
38 |
39 | public final void jumpTo(int location) {
40 | int realLocation = location - getWidth() / 2;
41 | if (mLastLocation == realLocation) return;
42 | if (set.isRunning()) set.cancel();
43 | createAnimator(realLocation).start();
44 | mLastLocation = realLocation;
45 | }
46 |
47 | public final AnimatorSet createAnimator(int location) {
48 | ObjectAnimator translationX = ObjectAnimator.ofFloat(this, "translationX", (float) mLastLocation, (float) location);
49 | ObjectAnimator rotationY = ObjectAnimator.ofFloat(this, "rotationY", 0.0F, mLastLocation > location ? -180.0F : 180.0F, 0.0F);
50 | ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 1.0F, 0.2F, 1.0F);
51 | set.setDuration(mDuration);
52 | set.playTogether(translationX, rotationY, scaleX);
53 | return set;
54 | }
55 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/java/com/hxxdashai/android/navigation/widget/NavigationLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.ColorStateList;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.graphics.Rect;
8 | import android.support.v4.view.ViewCompat;
9 | import android.text.TextUtils;
10 | import android.util.AttributeSet;
11 | import android.util.SparseIntArray;
12 | import android.view.Gravity;
13 | import android.view.KeyEvent;
14 | import android.view.View;
15 | import android.view.ViewTreeObserver;
16 | import android.widget.LinearLayout;
17 | import android.widget.TextView;
18 |
19 | import com.hxxdashai.android.navigation.R;
20 | import com.hxxdashai.android.navigation.util.SoundUtil;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | /**
26 | * Created by Mr.T on 2018/4/25.
27 | */
28 |
29 | public class NavigationLinearLayout extends LinearLayout {
30 |
31 | public static final int STATE_NO_SELECT = 666;//默认状态
32 | public static final int STATE_HAS_SELECT_NO_fOCUS = 667;//选中无焦点
33 | public static final int STATE_HAS_SELECT_HAS_fOCUS = 668;//选中有焦点
34 |
35 | public static final String MODE_SAME = "same";//item固定宽模式
36 | public static final String MODE_SELF = "self";//item自适应宽模式
37 |
38 | private float fontSize;//字体大小
39 | private float enlargeRate;//放大倍率
40 | private int fontColorNormal;//默认字体颜色
41 | private int fontColorSelect;//选中字体颜色
42 | private int fontColorLight;//选中字体发光颜色
43 | private int defaultPos;//默认选中的pos
44 | private String orderMode;//item排列模式,"same":固定宽模式,"self":自适应宽模式
45 | private int itemSpace;//"same":item宽度,"self":item距左或右宽度(实际每个item间距是两个itemSpace值)
46 |
47 | private List mDataList = new ArrayList();
48 | private SparseIntArray mToLeftMap = new SparseIntArray();
49 | private int mNowPos = -1;
50 | private NavigationListener mNavigationListener;
51 | private NavigationCursorView mNavigationCursorView;
52 |
53 | public NavigationLinearLayout(Context context) {
54 | this(context, null);
55 | }
56 |
57 | public NavigationLinearLayout(Context context, AttributeSet attrs) {
58 | this(context, attrs, 0);
59 | }
60 |
61 | public NavigationLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
62 | super(context, attrs, defStyleAttr);
63 | if (attrs != null) {
64 | TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.home_Navigation);
65 | fontSize = attributes.getDimension(R.styleable.home_Navigation_home_FontSize, 30.0F);
66 | enlargeRate = attributes.getFloat(R.styleable.home_Navigation_home_EnlargeRate, 1.1F);
67 | fontColorNormal = attributes.getColor(R.styleable.home_Navigation_home_FontColorNormal, Color.WHITE);
68 | fontColorSelect = attributes.getColor(R.styleable.home_Navigation_home_FontColorSelect, Color.BLUE);
69 | fontColorLight = attributes.getColor(R.styleable.home_Navigation_home_FontColorLight, Color.RED);
70 | defaultPos = attributes.getInteger(R.styleable.home_Navigation_home_DefaultPos, 0);
71 | String mode = attributes.getString(R.styleable.home_Navigation_home_OrderMode);
72 | if (TextUtils.isEmpty(mode)) mode = "self";
73 | orderMode = mode;
74 | itemSpace = attributes.getDimensionPixelSize(R.styleable.home_Navigation_home_ItemSpace, 10);
75 | attributes.recycle();
76 | }
77 | setFocusable(true);
78 | }
79 |
80 | /**
81 | * 设置数据,数据改变后需重新调用(导航栏编辑等功能)
82 | */
83 | public void setDataList(List data) {
84 | mDataList = data;
85 | initView();
86 | }
87 |
88 | /**
89 | * 监听导航事件
90 | */
91 | public void setNavigationListener(NavigationListener listener) {
92 | mNavigationListener = listener;
93 | if (mNavigationListener != null) {
94 | mNavigationListener.onNavigationChange(mNowPos, KeyEvent.KEYCODE_DPAD_LEFT);
95 | }
96 | }
97 |
98 | /**
99 | * 导航光标
100 | */
101 | public void setNavigationCursorView(NavigationCursorView view) {
102 | mNavigationCursorView = view;
103 | int left = mToLeftMap.get(mNowPos);
104 | if (left != 0 && mNavigationCursorView != null) {
105 | mNavigationCursorView.fsatJumpTo(left);
106 | }
107 | }
108 |
109 | private void initView() {
110 | if (mToLeftMap.size() != 0) {
111 | mToLeftMap.clear();
112 | }
113 | if (mDataList.size() > getChildCount()) {
114 | do {
115 | addView(getItemView());
116 | } while (mDataList.size() > getChildCount());
117 | } else if (mDataList.size() < getChildCount()) {
118 | do {
119 | removeViewAt(getChildCount() - 1);
120 | } while (mDataList.size() < getChildCount());
121 | }
122 | if (mNowPos != -1 && mNowPos < getChildCount()) {
123 | changeItemState(mNowPos, STATE_NO_SELECT);
124 | }
125 | for (int i = 0; i <= getChildCount() - 1; i++) {
126 | final int finalI = i;
127 | final TextView child = (TextView) getChildAt(i);
128 | child.setText(mDataList.get(i));
129 | child.getViewTreeObserver().addOnGlobalLayoutListener((new ViewTreeObserver.OnGlobalLayoutListener() {
130 | public void onGlobalLayout() {
131 | child.getViewTreeObserver().removeOnGlobalLayoutListener(this);
132 | int left = child.getWidth() / 2 + child.getLeft() + getLeft();//每个item中点到父布局左边的距离
133 | mToLeftMap.put(finalI, left);
134 | if (defaultPos == finalI) {
135 | mNowPos = defaultPos;
136 | changeItemState(mNowPos, isFocused() ? STATE_HAS_SELECT_HAS_fOCUS : STATE_HAS_SELECT_NO_fOCUS);
137 | if (mNavigationCursorView != null) {
138 | mNavigationCursorView.fsatJumpTo(left);
139 | }
140 | if (mNavigationListener != null) {
141 | mNavigationListener.onNavigationChange(mNowPos, KeyEvent.KEYCODE_DPAD_LEFT);
142 | }
143 | }
144 | }
145 | }));
146 | }
147 | }
148 |
149 | private void changeItemState(int pos, int state) {
150 | View child = getChildAt(pos);
151 | if (child != null) {
152 | switch (state) {
153 | case STATE_NO_SELECT:
154 | //if (child.scaleX != 1f)//TODO BUG
155 | ViewCompat.animate(child).scaleX(1.0F).scaleY(1.0F).translationZ(0.0F).start();
156 | ((TextView) child).setShadowLayer(0.0F, 0.0F, 0.0F, fontColorLight);
157 | child.setSelected(false);
158 | break;
159 | case STATE_HAS_SELECT_NO_fOCUS:
160 | if (child.getScaleX() != 1.0F)
161 | ViewCompat.animate(child).scaleX(1.0F).scaleY(1.0F).translationZ(0.0F).start();
162 | if (!child.isSelected()) {
163 | ((TextView) child).setShadowLayer(25.0F, 0.0F, 0.0F, fontColorLight);
164 | child.setSelected(true);
165 | }
166 | break;
167 | case STATE_HAS_SELECT_HAS_fOCUS:
168 | ViewCompat.animate(child).scaleX(enlargeRate).scaleY(enlargeRate).translationZ(0.0F).start();
169 | if (!child.isSelected()) {
170 | ((TextView) child).setShadowLayer(25.0F, 0.0F, 0.0F, fontColorLight);
171 | child.setSelected(true);
172 | }
173 | break;
174 | }
175 | }
176 | }
177 |
178 | public boolean onKeyDown(int keyCode, KeyEvent event) {
179 | if (event.getAction() == KeyEvent.ACTION_DOWN) {
180 | switch (keyCode) {
181 | case KeyEvent.KEYCODE_DPAD_LEFT:
182 | if (mNowPos > 0) {
183 | changeItemState(mNowPos, STATE_NO_SELECT);
184 | mNowPos--;
185 | changeItemState(mNowPos, STATE_HAS_SELECT_HAS_fOCUS);
186 | int left = mToLeftMap.get(mNowPos);
187 | if (left != 0 && mNavigationCursorView != null)
188 | mNavigationCursorView.jumpTo(left);
189 | if (mNavigationListener != null) {
190 | mNavigationListener.onNavigationChange(mNowPos, keyCode);
191 | }
192 | }
193 | SoundUtil.playClickSound(this);
194 | return true;
195 | case KeyEvent.KEYCODE_DPAD_RIGHT:
196 | if (mNowPos < getChildCount() - 1) {
197 | changeItemState(mNowPos, STATE_NO_SELECT);
198 | mNowPos++;
199 | changeItemState(mNowPos, STATE_HAS_SELECT_HAS_fOCUS);
200 | int left = mToLeftMap.get(mNowPos);
201 | if (left != 0 && mNavigationCursorView != null)
202 | mNavigationCursorView.jumpTo(left);
203 | if (mNavigationListener != null) {
204 | mNavigationListener.onNavigationChange(mNowPos, keyCode);
205 | }
206 | }
207 | SoundUtil.playClickSound(this);
208 | return true;
209 | case KeyEvent.KEYCODE_DPAD_UP:
210 | case KeyEvent.KEYCODE_DPAD_DOWN:
211 | if (mNavigationListener != null) {
212 | mNavigationListener.onNavigationChange(mNowPos, keyCode);
213 | }
214 | break;
215 | case KeyEvent.KEYCODE_MENU:
216 | if (mNavigationListener != null) {
217 | mNavigationListener.onNavigationChange(mNowPos, keyCode);
218 | }
219 | return true;
220 | }
221 | }
222 | return super.onKeyDown(keyCode, event);
223 | }
224 |
225 | protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
226 | changeItemState(mNowPos, gainFocus ? STATE_HAS_SELECT_HAS_fOCUS : STATE_HAS_SELECT_NO_fOCUS);
227 | if (mNavigationCursorView != null)
228 | mNavigationCursorView.setVisibility(gainFocus ? View.VISIBLE : View.INVISIBLE);
229 | super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
230 | }
231 |
232 | private TextView getItemView() {
233 | int[][] states = {{android.R.attr.state_selected}, new int[0]};
234 | int[] colors = new int[]{fontColorSelect, fontColorNormal};
235 | ColorStateList colorStateList = new ColorStateList(states, colors);
236 | TextView textView = new TextView(getContext());
237 | textView.setTextSize(fontSize);
238 | textView.setTextColor(colorStateList);
239 | textView.setIncludeFontPadding(false);
240 | switch (orderMode) {
241 | case MODE_SAME:
242 | LayoutParams layoutParams = new LayoutParams(itemSpace, LayoutParams.WRAP_CONTENT);
243 | textView.setLayoutParams(layoutParams);
244 | textView.setGravity(Gravity.CENTER);
245 | break;
246 | case MODE_SELF:
247 | textView.setPadding(itemSpace, 0, itemSpace, 0);
248 | }
249 | return textView;
250 | }
251 |
252 | public void jumpTo(int keyCode) {
253 | if (mNowPos > 0 && keyCode == KeyEvent.KEYCODE_DPAD_LEFT || mNowPos < getChildCount() - 1 && keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
254 | changeItemState(mNowPos, STATE_NO_SELECT);
255 | if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
256 | mNowPos--;
257 | else
258 | mNowPos++;
259 | changeItemState(mNowPos, STATE_HAS_SELECT_NO_fOCUS);
260 | int left = mToLeftMap.get(mNowPos);
261 | if (left != 0 && mNavigationCursorView != null)
262 | mNavigationCursorView.jumpTo(left);
263 | if (mNavigationListener != null) {
264 | mNavigationListener.onNavigationChange(mNowPos, keyCode);
265 | }
266 | }
267 | }
268 |
269 | public interface NavigationListener {
270 |
271 | void onNavigationChange(int pos, int keyCode);
272 | }
273 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/drawable/selector_text_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
39 |
40 |
46 |
47 |
60 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/mipmap-xxhdpi/bg_main.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/TvNavigationLayout-java/app/src/main/res/mipmap-xxhdpi/bg_main.jpg
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/TvNavigationLayout-java/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/mipmap-xxhdpi/ic_navigation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/TvNavigationLayout-java/app/src/main/res/mipmap-xxhdpi/ic_navigation.png
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Navigation-master
3 |
4 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.0.0'
9 | // NOTE: Do not place your application dependencies here; they belong
10 | // in the individual module build.gradle files
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | }
18 | }
19 |
20 | task clean(type: Delete) {
21 | delete rootProject.buildDir
22 | }
23 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/TvNavigationLayout-java/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 26
7 | defaultConfig {
8 | applicationId "com.hxxdashai.android.navigation"
9 | minSdkVersion 23
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
24 | implementation 'com.android.support:support-v4:26.1.0'
25 | }
26 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/androidTest/java/com/hxxdashai/android/navigation/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/java/com/hxxdashai/android/navigation/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.view.KeyEvent
6 | import com.hxxdashai.android.navigation.widget.NavigationLinearLayout
7 | import kotlinx.android.synthetic.main.activity_main.*
8 |
9 | /**
10 | * Created by Mr.T on 2018/3/29.
11 | */
12 | class MainActivity : Activity() {
13 |
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | setContentView(R.layout.activity_main)
17 | mNavigationLinearLayout_id.mDataList = arrayListOf("我的电视", "影视", "教育", "游戏", "应用", "动漫", "少儿", "VIP专区")
18 | mNavigationLinearLayout_id.mNavigationListener = mNavigationListener
19 | mNavigationLinearLayout_id.mNavigationCursorView = mNavigationCursorView_id
20 | mNavigationLinearLayout_id.requestFocus()
21 | mContent.setOnKeyListener { _, keyCode, event ->
22 | if (event?.action == KeyEvent.ACTION_DOWN) {
23 | when (keyCode) {
24 | KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT -> {//此处需要换页面时候的焦点查询
25 | mNavigationLinearLayout_id.jumpTo(keyCode)
26 | }
27 | }
28 | }
29 | false
30 | }
31 | }
32 |
33 | private val mNavigationListener = object : NavigationLinearLayout.NavigationListener {
34 | override fun onNavigationChange(pos: Int, keyCode: Int) {
35 | when (keyCode) {
36 | KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT -> {//模拟刷新内容区域
37 | mContent.text = "我是内容显示区域,当前页面为:$pos,左右切换内容,上键回到导航栏"
38 | }
39 | KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN -> {
40 |
41 | }
42 | KeyEvent.KEYCODE_MENU -> {//模拟重新编辑刷新了导航栏栏目数据
43 | mNavigationLinearLayout_id.mDataList = arrayListOf("分栏1", "分栏2", "分栏3", "分栏4", "分栏5", "分栏6", "分栏7", "分栏8")
44 | }
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/java/com/hxxdashai/android/navigation/util/SoundUtil.kt:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation.util
2 |
3 | import android.content.Context
4 | import android.media.AudioManager
5 | import android.view.View
6 |
7 | /**
8 | * Created by Mr.T on 2018/3/30.
9 | */
10 | object SoundUtil {
11 |
12 | fun playClickSound(view: View) {
13 | if (view.isSoundEffectsEnabled) {
14 | val manager = view.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
15 | manager.playSoundEffect(AudioManager.FX_KEY_CLICK)
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/java/com/hxxdashai/android/navigation/widget/NavigationCursorView.kt:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation.widget
2 |
3 | import android.animation.AnimatorSet
4 | import android.animation.ObjectAnimator
5 | import android.content.Context
6 | import android.util.AttributeSet
7 | import android.view.View
8 | import android.widget.ImageView
9 |
10 | /**
11 | * Created by Mr.T on 2018/3/29.
12 | */
13 | class NavigationCursorView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ImageView(context, attrs, defStyleAttr) {
14 |
15 | init {
16 | visibility = View.INVISIBLE
17 | }
18 |
19 | private var mDuration = 0L
20 | private var mLastLocation = 0
21 | private val set = AnimatorSet()
22 |
23 | /**
24 | * 光标切换位置时候调用(忽略动画)初始化时候调用
25 | */
26 | fun fsatJumpTo(location: Int) {
27 | mDuration = 1L
28 | jumpTo(location)
29 | mDuration = 200L
30 | }
31 |
32 | /**
33 | * 光标切换位置时候调用(有动画)
34 | */
35 | fun jumpTo(location: Int) {
36 | val realLocation = location - width / 2
37 | if (mLastLocation == realLocation) return
38 | if (set.isRunning) set.cancel()
39 | createAnimator(realLocation).start()
40 | mLastLocation = realLocation
41 | }
42 |
43 | fun createAnimator(location: Int): AnimatorSet {
44 | val translationX = ObjectAnimator.ofFloat(this, "translationX", mLastLocation.toFloat(), location.toFloat())
45 | val rotationY = ObjectAnimator.ofFloat(this, "rotationY", 0.0f, if (mLastLocation > location) -180.0f else 180.0f, 0.0f)
46 | val scaleX = ObjectAnimator.ofFloat(this, "scaleX", 1f, 0.2f, 1f)
47 | set.duration = mDuration
48 | set.playTogether(translationX, rotationY, scaleX)
49 | return set
50 | }
51 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/java/com/hxxdashai/android/navigation/widget/NavigationLinearLayout.kt:
--------------------------------------------------------------------------------
1 | package com.hxxdashai.android.navigation.widget
2 |
3 | import android.content.Context
4 | import android.content.res.ColorStateList
5 | import android.graphics.Color
6 | import android.graphics.Rect
7 | import android.support.v4.view.ViewCompat
8 | import android.util.AttributeSet
9 | import android.view.Gravity
10 | import android.view.KeyEvent
11 | import android.view.View
12 | import android.view.ViewTreeObserver
13 | import android.widget.LinearLayout
14 | import android.widget.TextView
15 | import com.hxxdashai.android.navigation.R
16 | import com.hxxdashai.android.navigation.util.SoundUtil
17 |
18 | /**
19 | * Created by Mr.T on 2018/3/27.
20 | */
21 | class NavigationLinearLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
22 |
23 | companion object {
24 | const val STATE_NO_SELECT = 666//默认状态
25 | const val STATE_HAS_SELECT_NO_fOCUS = 667//选中无焦点
26 | const val STATE_HAS_SELECT_HAS_fOCUS = 668//选中有焦点
27 |
28 | const val MODE_SAME = "same"//item固定宽模式
29 | const val MODE_SELF = "self"//item自适应宽模式
30 | }
31 |
32 | private var fontSize: Float = 0.0F//字体大小
33 | private var enlargeRate: Float = 0.0f//放大倍率
34 | private var fontColorNormal: Int = 0//默认字体颜色
35 | private var fontColorSelect: Int = 0//选中字体颜色
36 | private var fontColorLight: Int = 0//选中字体发光颜色
37 | private var defaultPos: Int = 0//默认选中的pos
38 | private var orderMode: String = ""//item排列模式,"same":固定宽模式,"self":自适应宽模式
39 | private var itemSpace: Int = 0//"same":item宽度,"self":item距左或右宽度(实际每个item间距是两个itemSpace值)
40 |
41 | init {
42 | if (attrs != null) {
43 | val attributes = context.obtainStyledAttributes(attrs, R.styleable.home_Navigation)
44 | fontSize = attributes.getDimension(R.styleable.home_Navigation_home_FontSize, 30f)
45 | enlargeRate = attributes.getFloat(R.styleable.home_Navigation_home_EnlargeRate, 1.1f)
46 | fontColorNormal = attributes.getColor(R.styleable.home_Navigation_home_FontColorNormal, Color.WHITE)
47 | fontColorSelect = attributes.getColor(R.styleable.home_Navigation_home_FontColorSelect, Color.BLUE)
48 | fontColorLight = attributes.getColor(R.styleable.home_Navigation_home_FontColorLight, Color.RED)
49 | defaultPos = attributes.getInteger(R.styleable.home_Navigation_home_DefaultPos, 0)
50 | orderMode = attributes.getString(R.styleable.home_Navigation_home_OrderMode) ?: MODE_SELF
51 | itemSpace = attributes.getDimensionPixelSize(R.styleable.home_Navigation_home_ItemSpace, 10)
52 | attributes.recycle()
53 | }
54 | isFocusable = true
55 | }
56 |
57 | /**
58 | * 设置数据,数据改变后需重新调用(导航栏编辑等功能)
59 | */
60 | var mDataList: MutableList = ArrayList()
61 | set(value) {
62 | field = value
63 | initView()
64 | }
65 | private var mToLeftMap: MutableMap = HashMap()//每个item中点到父布局左边的距离
66 | var mNowPos: Int = -1
67 | /**
68 | * 监听导航事件
69 | */
70 | var mNavigationListener: NavigationListener? = null
71 | set(value) {
72 | field = value
73 | field?.onNavigationChange(mNowPos, KeyEvent.KEYCODE_DPAD_LEFT)
74 | }
75 | /**
76 | * 导航光标
77 | */
78 | var mNavigationCursorView: NavigationCursorView? = null
79 | set(value) {
80 | field = value
81 | mToLeftMap[mNowPos]?.let { field?.fsatJumpTo(it) }
82 | }
83 |
84 | private fun initView() {
85 | if (mToLeftMap.isNotEmpty()) mToLeftMap.clear()//还原状态
86 | if (mDataList.size > childCount) {
87 | do {
88 | addView(getItemView())
89 | } while (mDataList.size > childCount)
90 | } else if (mDataList.size < childCount) {
91 | do {
92 | removeViewAt(childCount - 1)
93 | } while (mDataList.size < childCount)
94 | }
95 | if (mNowPos != -1 && mNowPos < childCount) changeItemState(mNowPos, STATE_NO_SELECT)//还原状态
96 | for (i in 0..(childCount - 1)) {
97 | val child = getChildAt(i) as TextView
98 | child.text = mDataList[i]
99 | child.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
100 | override fun onGlobalLayout() {
101 | child.viewTreeObserver.removeOnGlobalLayoutListener(this)
102 | mToLeftMap[i] = child.width / 2 + child.left + this@NavigationLinearLayout.left//每个item中点到父布局左边的距离
103 | if (defaultPos == i) {//TODO 如果编辑导航后不要重置pos,可根据实际修改逻辑
104 | mNowPos = defaultPos//默认要展示的pos
105 | changeItemState(mNowPos, if (this@NavigationLinearLayout.isFocused) STATE_HAS_SELECT_HAS_fOCUS else STATE_HAS_SELECT_NO_fOCUS)//修改默认要展示的pos的状态
106 | mToLeftMap[mNowPos]?.let { mNavigationCursorView?.fsatJumpTo(it) }//移动光标
107 | mNavigationListener?.onNavigationChange(mNowPos, KeyEvent.KEYCODE_DPAD_LEFT)//展示内容数据,仅仅展示数据,写左右都没问题
108 | }
109 | }
110 | })
111 | }
112 | }
113 |
114 | private fun changeItemState(pos: Int, state: Int) {
115 | val child = getChildAt(pos)
116 | if (child != null)
117 | when (state) {
118 | STATE_NO_SELECT -> {
119 | //if (child.scaleX != 1f) ViewCompat.animate(child).scaleX(1f).scaleY(1f).translationZ(0f).start()//TODO BUG
120 | ViewCompat.animate(child).scaleX(1f).scaleY(1f).translationZ(0f).start()
121 | (child as TextView).setShadowLayer(0f, 0f, 0f, fontColorLight)
122 | child.isSelected = false
123 | }
124 | STATE_HAS_SELECT_NO_fOCUS -> {
125 | if (child.scaleX != 1f) ViewCompat.animate(child).scaleX(1f).scaleY(1f).translationZ(0f).start()
126 | if (!child.isSelected) {
127 | (child as TextView).setShadowLayer(25f, 0f, 0f, fontColorLight)
128 | child.isSelected = true
129 | }
130 | }
131 | STATE_HAS_SELECT_HAS_fOCUS -> {
132 | ViewCompat.animate(child).scaleX(enlargeRate).scaleY(enlargeRate).translationZ(0f).start()
133 | if (!child.isSelected) {
134 | (child as TextView).setShadowLayer(25f, 0f, 0f, fontColorLight)
135 | child.isSelected = true
136 | }
137 | }
138 | }
139 | }
140 |
141 | override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
142 | if (event?.action == KeyEvent.ACTION_DOWN) {
143 | when (keyCode) {
144 | KeyEvent.KEYCODE_DPAD_LEFT -> {
145 | if (mNowPos > 0) {
146 | changeItemState(mNowPos, STATE_NO_SELECT)
147 | changeItemState(--mNowPos, STATE_HAS_SELECT_HAS_fOCUS)
148 | mToLeftMap[mNowPos]?.let { mNavigationCursorView?.jumpTo(it) }
149 | mNavigationListener?.onNavigationChange(mNowPos, keyCode)
150 | }//如果有跳出导航栏的左右事件需求可在次此处else回调出去
151 | SoundUtil.playClickSound(this@NavigationLinearLayout)
152 | return true//TODO 系统声音会被屏蔽掉
153 | }
154 | KeyEvent.KEYCODE_DPAD_RIGHT -> {
155 | if (mNowPos < childCount - 1) {
156 | changeItemState(mNowPos, STATE_NO_SELECT)
157 | changeItemState(++mNowPos, STATE_HAS_SELECT_HAS_fOCUS)
158 | mToLeftMap[mNowPos]?.let { mNavigationCursorView?.jumpTo(it) }
159 | mNavigationListener?.onNavigationChange(mNowPos, keyCode)
160 | }
161 | SoundUtil.playClickSound(this@NavigationLinearLayout)
162 | return true
163 | }
164 | KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN -> {//TODO 方向类型的事件,不想系统自动找焦点,可试试return true
165 | mNavigationListener?.onNavigationChange(mNowPos, keyCode)
166 | }
167 | KeyEvent.KEYCODE_MENU -> {//TODO 非方向类型事件
168 | mNavigationListener?.onNavigationChange(mNowPos, keyCode)
169 | return true//TODO bug
170 | }
171 | }
172 | }
173 | return super.onKeyDown(keyCode, event)
174 | }
175 |
176 | override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
177 | changeItemState(mNowPos, if (gainFocus) STATE_HAS_SELECT_HAS_fOCUS else STATE_HAS_SELECT_NO_fOCUS)
178 | mNavigationCursorView?.visibility = if (gainFocus) View.VISIBLE else View.INVISIBLE
179 | super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
180 | }
181 |
182 | private fun getItemView(): TextView {
183 | val states = arrayOf(intArrayOf(android.R.attr.state_selected), intArrayOf())
184 | val colors = intArrayOf(fontColorSelect, fontColorNormal)
185 | val colorStateList = ColorStateList(states, colors)
186 | val textView = TextView(context)
187 | textView.textSize = fontSize
188 | textView.setTextColor(colorStateList)
189 | textView.includeFontPadding = false
190 | when (orderMode) {
191 | MODE_SAME -> {
192 | val layoutParams = LayoutParams(itemSpace, LayoutParams.WRAP_CONTENT)
193 | textView.layoutParams = layoutParams
194 | textView.gravity = Gravity.CENTER
195 | }
196 | MODE_SELF -> {
197 | textView.setPadding(itemSpace, 0, itemSpace, 0)
198 | }
199 | }
200 | return textView
201 | }
202 |
203 | /**
204 | * 导航栏目被动切换时调用
205 | */
206 | fun jumpTo(keyCode: Int) {
207 | if ((mNowPos > 0 && keyCode == KeyEvent.KEYCODE_DPAD_LEFT) || (mNowPos < childCount - 1 && keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
208 | changeItemState(mNowPos, STATE_NO_SELECT)
209 | changeItemState(if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) --mNowPos else ++mNowPos, STATE_HAS_SELECT_NO_fOCUS)
210 | mToLeftMap[mNowPos]?.let { mNavigationCursorView?.jumpTo(it) }
211 | mNavigationListener?.onNavigationChange(mNowPos, KeyEvent.KEYCODE_DPAD_LEFT)//仅仅展示数据,写左右都没问题
212 | }
213 | }
214 |
215 | interface NavigationListener {
216 |
217 | /**
218 | * @param pos 选中的序号
219 | * @param keyCode 点击的按键
220 | */
221 | fun onNavigationChange(pos: Int, keyCode: Int)
222 | }
223 | }
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/drawable/selector_text_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 | -
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
24 |
25 |
39 |
40 |
46 |
47 |
60 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/mipmap-xxhdpi/bg_main.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/TvNavigationLayout-kotlin/app/src/main/res/mipmap-xxhdpi/bg_main.jpg
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/TvNavigationLayout-kotlin/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/mipmap-xxhdpi/ic_navigation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/TvNavigationLayout-kotlin/app/src/main/res/mipmap-xxhdpi/ic_navigation.png
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Navigation-master
3 |
4 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.2.30'
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.0'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/TvNavigationLayout-kotlin/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/gif/gif1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrTangFB/TvNavigationLayout/8543db6b31c60defca6ad220bc8d701d6d0eb779/gif/gif1.gif
--------------------------------------------------------------------------------