├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── zhangjy.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── fb │ │ └── jjyyzjy │ │ └── buildlayoutdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── fb │ │ │ └── jjyyzjy │ │ │ └── buildlayoutdemo │ │ │ ├── BuildLayoutAdapter.java │ │ │ ├── MainActivity.java │ │ │ ├── utils │ │ │ ├── CastUtils.java │ │ │ ├── DebugLog.java │ │ │ ├── DisplayUtil.java │ │ │ └── FocusScaleUtils.java │ │ │ └── view │ │ │ ├── bring │ │ │ ├── BringToFrontHelper.java │ │ │ ├── BringToFrontLinear.java │ │ │ ├── BringToFrontLinearLayout.java │ │ │ ├── BringToFrontRelative.java │ │ │ └── BringToFrontRelativeLayout.java │ │ │ └── buildview │ │ │ ├── BaseBuildLayoutAdapter.java │ │ │ ├── BuildAdapterDataObserver.java │ │ │ ├── BuildItemPoll.java │ │ │ ├── BuildLayout.java │ │ │ ├── BuildLayoutUtils.java │ │ │ ├── FocusSearchUtils.java │ │ │ ├── MyBuildLayout.java │ │ │ ├── OnDismissFocusListener2.java │ │ │ ├── OnOutOfBoundListener.java │ │ │ └── Parameter.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-1024x575 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1024x600 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1024x720 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1152x648 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1196x720 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1196x768 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1200x720 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1203x676 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1216x684 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1220x690 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1228x691 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1241x698 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1267x712 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1278x718 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1280x656 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1280x672 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1280x719 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1280x720 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1280x736 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1280x800 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1366x768 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1794x1080 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1820x1030 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1832x1028 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1840x1060 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1916x1076 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1916x1080 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1920x1008 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1920x1080 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-1920x1128 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-2048x1460 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-2560x1440 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-2560x1600 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-3840x2160 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-791x480 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-800x432 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-800x444 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-800x480 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-854x480 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-960x540 │ │ ├── dimens.xml │ │ └── strings.xml │ │ ├── values-960x640 │ │ ├── dimens.xml │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── fb │ └── jjyyzjy │ └── buildlayoutdemo │ └── ExampleUnitTest.java ├── build.gradle ├── build_layout_use_1.png ├── build_layout_use_2.png ├── build_use.gif ├── build_use.mp4 ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dictionaries/zhangjy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Android > Lint > Correctness 39 | 40 | 41 | Android > Lint > Security 42 | 43 | 44 | Class metricsJava 45 | 46 | 47 | Data flow issuesJava 48 | 49 | 50 | Error handlingJava 51 | 52 | 53 | HTML 54 | 55 | 56 | Java 57 | 58 | 59 | Method metricsJava 60 | 61 | 62 | Modularization issuesJava 63 | 64 | 65 | Security issuesJava 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 87 | 88 | $USER_HOME$/.subversion 89 | 90 | 91 | 92 | 93 | 94 | 95 | 100 | 101 | 102 | 103 | 104 | 105 | 1.8 106 | 107 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidTvLayout 2 | ## TV动态布局 Android TV Layout BuildLayout DynamicLayout 3 | ###### 使用简单 4 | ###### item复用 5 | ###### 布局改变焦点不会乱 6 | ###### 支持控制焦点移动 7 | ###### 支持item缩放 8 | ###### 支持xml和动态设置布局大小item宽高间距等 9 | 10 | ```xml 11 | 24 | ``` 25 | ```java 26 | int[][] data = {{0,0,1,1},{0,1,1,1},{0,2,1,1},{1,0,2,1},{1,1,2,1},{1,2,1,1},{2,2,1,1},{3,0,2,2},{3,2,2,1},{5,0,1,3}}; 27 | MyBuildLayout buildLayout = (MyBuildLayout) findViewById(R.id.build_layout); 28 | BuildLayoutAdapter adapter = new BuildLayoutAdapter(); 29 | buildLayout.setAdapter(adapter); 30 | adapter.setData(data); 31 | adapter.notifyDataSetChanged(); 32 | 33 | buildLayout.mySetLayoutSize(6,3); 34 | buildLayout.mySetItemSize(250),151); 35 | buildLayout.mySetDividing(30); 36 | ``` 37 | ![build_use.gif](build_use.gif) 38 | 39 | ![build_layout_use_1.png](build_layout_use_1.png) 40 | 41 | ![build_layout_use_2.png](build_layout_use_2.png) 42 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.2" 6 | defaultConfig { 7 | applicationId "com.fb.jjyyzjy.buildlayoutdemo" 8 | minSdkVersion 15 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:26.+' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testCompile 'junit:junit:4.12' 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zhangjy/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/fb/jjyyzjy/buildlayoutdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.fb.jjyyzjy.buildlayoutdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/BuildLayoutAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo; 2 | 3 | import android.graphics.Color; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.ImageView; 7 | 8 | import com.fb.jjyyzjy.buildlayoutdemo.view.buildview.BaseBuildLayoutAdapter; 9 | 10 | /** 11 | * Created by zhangjy on 2018/4/24. 12 | */ 13 | 14 | public class BuildLayoutAdapter extends BaseBuildLayoutAdapter { 15 | 16 | private int[][] data; 17 | 18 | public void setData(int[][] data){ 19 | this.data = data; 20 | } 21 | 22 | @Override 23 | public int[] getLayout(int position) { 24 | return data[position]; 25 | } 26 | 27 | @Override 28 | public int getCount() { 29 | return data == null ? 0 : data.length; 30 | } 31 | 32 | @Override 33 | public Object getItem(int position) { 34 | return data[position]; 35 | } 36 | 37 | @Override 38 | public long getItemId(int position) { 39 | return position; 40 | } 41 | 42 | @Override 43 | public View getView(int position, View contentView, ViewGroup parent) { 44 | if (contentView == null){ 45 | contentView = new ImageView(parent.getContext()); 46 | contentView.setBackgroundColor(Color.BLUE); 47 | } 48 | return contentView; 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.fb.jjyyzjy.buildlayoutdemo.utils.DisplayUtil; 9 | import com.fb.jjyyzjy.buildlayoutdemo.view.buildview.MyBuildLayout; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | DisplayUtil.init(this); 18 | initView(); 19 | } 20 | 21 | private int[][] data = {{0,0,1,1},{0,1,1,1},{0,2,1,1},{1,0,2,1},{1,1,2,1},{1,2,1,1},{2,2,1,1},{3,0,2,2},{3,2,2,1},{5,0,1,3}}; 22 | private int[][] data2 = {{0,0,3,3},{3,0,1,1},{3,1,1,1},{3,2,1,1},{4,0,1,2},{4,2,1,1},{5,0,1,1},{5,1,1,2},{0,3,6,1}}; 23 | private boolean isData = true; 24 | private void initView() { 25 | final MyBuildLayout buildLayout = (MyBuildLayout) findViewById(R.id.build_layout); 26 | final BuildLayoutAdapter adapter = new BuildLayoutAdapter(); 27 | buildLayout.setAdapter(adapter); 28 | adapter.setData(data); 29 | adapter.notifyDataSetChanged(); 30 | 31 | TextView btn = (TextView) findViewById(R.id.btn); 32 | btn.setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | if (isData = !isData){ 36 | buildLayout.mySetLayoutSize(6,3); 37 | buildLayout.mySetItemSize(DisplayUtil.dim2px(250),DisplayUtil.dim2px(151)); 38 | buildLayout.mySetDividing(DisplayUtil.dim2px(30)); 39 | adapter.setData(data); 40 | }else { 41 | buildLayout.mySetLayoutSize(7,4); 42 | buildLayout.mySetItemSize(DisplayUtil.dim2px(200),DisplayUtil.dim2px(100)); 43 | buildLayout.mySetDividing(DisplayUtil.dim2px(40)); 44 | adapter.setData(data2); 45 | } 46 | adapter.notifyDataSetChanged(); 47 | } 48 | }); 49 | btn.requestFocus(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/utils/CastUtils.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.utils; 2 | 3 | /** 4 | * 安全的类型转换 5 | * Created by ZJY on 2016/5/19. 6 | */ 7 | public class CastUtils { 8 | 9 | public static int getIntForString(String str, int defaultNum){ 10 | try{ 11 | defaultNum = Integer.parseInt(str); 12 | }catch (Exception e){ 13 | DebugLog.e("CastUtils_getIntForString Exception=="+e); 14 | } 15 | return defaultNum; 16 | } 17 | 18 | public static long getLongForString(String str, long defaultNum){ 19 | try{ 20 | defaultNum = Long.parseLong(str); 21 | }catch (Exception e){ 22 | DebugLog.e("CastUtils_getLongForString Exception=="+e); 23 | } 24 | return defaultNum; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/utils/DebugLog.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.utils; 2 | 3 | import android.util.Log; 4 | 5 | public class DebugLog { 6 | 7 | private static String LOG_TAG = "DEBUG_LOG"; 8 | private static String className; 9 | private static String methodName; 10 | private static int lineNumber; 11 | private static boolean isDebug = true; 12 | 13 | public static void initDebug(boolean isDebug) { 14 | DebugLog.isDebug = isDebug; 15 | } 16 | 17 | private static boolean isDebug() { 18 | return isDebug; 19 | } 20 | 21 | private static String createLog(String log) { 22 | return "[" + 23 | className + 24 | ":" + 25 | methodName + 26 | "+" + 27 | lineNumber + 28 | "]" + 29 | log; 30 | } 31 | 32 | private static void getMethodNames(StackTraceElement[] sElements) { 33 | className = sElements[1].getFileName(); 34 | methodName = sElements[1].getMethodName(); 35 | lineNumber = sElements[1].getLineNumber(); 36 | } 37 | 38 | public static void e(String message) { 39 | if (isDebug()) { 40 | getMethodNames((new Throwable()).getStackTrace()); 41 | Log.e(LOG_TAG, createLog(message)); 42 | } 43 | } 44 | 45 | public static void i(String message) { 46 | if (isDebug()) { 47 | getMethodNames((new Throwable()).getStackTrace()); 48 | Log.i(LOG_TAG, createLog(message)); 49 | } 50 | } 51 | 52 | public static void d(String message) { 53 | if (isDebug()) { 54 | getMethodNames((new Throwable()).getStackTrace()); 55 | Log.d(LOG_TAG, createLog(message)); 56 | } 57 | } 58 | 59 | public static void v(String message) { 60 | if (isDebug()) { 61 | getMethodNames((new Throwable()).getStackTrace()); 62 | Log.v(LOG_TAG, createLog(message)); 63 | } 64 | } 65 | 66 | public static void w(String message) { 67 | if (isDebug()) { 68 | getMethodNames((new Throwable()).getStackTrace()); 69 | Log.w(LOG_TAG, createLog(message)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/utils/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.utils; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | 6 | /** 7 | * Created by JJYYZJY on 2017/4/25. 8 | */ 9 | public class DisplayUtil { 10 | private static float scale; 11 | 12 | private static float scaledDensity; 13 | 14 | private static float widthPixels; 15 | 16 | private static float heightPixels; 17 | 18 | public static final int CHINESE = 0; 19 | 20 | public static final int NUMBER_OR_CHARACTER = 1; 21 | 22 | public static void init(Context context){ 23 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 24 | scale = displayMetrics.density; 25 | scaledDensity = displayMetrics.scaledDensity; 26 | widthPixels = displayMetrics.widthPixels; 27 | heightPixels = displayMetrics.heightPixels; 28 | 29 | } 30 | 31 | public static int dim2px(float dimValue){ 32 | return (int) (dimValue*widthPixels/1920); 33 | } 34 | 35 | /** 36 | * dp转成px 37 | * @param dipValue 38 | * @return 39 | */ 40 | public static int dip2px(float dipValue) { 41 | return (int) (dipValue * scale + 0.5f); 42 | } 43 | 44 | /** 45 | * px转成dp 46 | * @param pxValue 47 | * @return 48 | */ 49 | public static int px2dip(float pxValue) { 50 | return (int) (pxValue / scale + 0.5f); 51 | } 52 | 53 | /** 54 | * sp转成px 55 | * @param spValue 56 | * @param type 57 | * @return 58 | */ 59 | public static float sp2px(float spValue, int type) { 60 | switch (type) { 61 | case CHINESE: 62 | return spValue * scaledDensity; 63 | case NUMBER_OR_CHARACTER: 64 | return spValue * scaledDensity * 10.0f / 18.0f; 65 | default: 66 | return spValue * scaledDensity; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/utils/FocusScaleUtils.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.utils; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.view.View; 7 | import android.view.animation.AccelerateInterpolator; 8 | import android.view.animation.DecelerateInterpolator; 9 | import android.view.animation.Interpolator; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * 焦点缩放工具类 16 | * Created by ZJY on 2016/6/7. 17 | * update on 2016/8/17 解决还原未执行完时放大失败问题 18 | */ 19 | public class FocusScaleUtils { 20 | private View oldView; 21 | /** 22 | * 放大用时 23 | */ 24 | private int durationLarge = 300; 25 | /** 26 | * 缩小用时 27 | */ 28 | private int durationSmall = 500; 29 | private float scale = 1.1f; 30 | private AnimatorSet animatorSet; 31 | private Interpolator interpolatorLarge; 32 | private Interpolator interpolatorSmall; 33 | private Map animatorSetMap; 34 | 35 | public FocusScaleUtils() { 36 | this.interpolatorLarge = new AccelerateInterpolator(1.5f); 37 | this.interpolatorSmall = new DecelerateInterpolator(1.5f); 38 | init(); 39 | } 40 | 41 | public FocusScaleUtils(int duration, float scale, Interpolator interpolator) { 42 | this(duration, duration, scale, interpolator, interpolator); 43 | } 44 | 45 | public FocusScaleUtils(int durationLarge, int durationSmall, float scale, Interpolator interpolatorLarge, Interpolator interpolatorSmall) { 46 | this.durationLarge = durationLarge; 47 | this.durationSmall = durationSmall; 48 | this.scale = scale; 49 | this.interpolatorLarge = interpolatorLarge; 50 | this.interpolatorSmall = interpolatorSmall; 51 | init(); 52 | } 53 | 54 | private void init() { 55 | animatorSetMap = new HashMap<>(); 56 | } 57 | 58 | /** 59 | * 放大指定view(view必须拥有焦点拥有焦点) 60 | * @param item 61 | */ 62 | public void scaleToLarge(View item) { 63 | if (!item.isFocused()) { 64 | return; 65 | } 66 | scaleToLargeNotFocus(item); 67 | } 68 | 69 | /** 70 | * 放大指定view(不需要view拥有焦点) 71 | * @param item 72 | */ 73 | public void scaleToLargeNotFocus(View item) { 74 | DebugLog.e("FocusScaleUtils__scaleToLargeNotFocus=="+item); 75 | DebugLog.e("FocusScaleUtils__animator_map.size=="+animatorSetMap.size()); 76 | 77 | Animator animatorOld = animatorSetMap.get(item); 78 | if (animatorOld != null && animatorOld.isRunning()){ 79 | animatorOld.cancel(); 80 | } 81 | 82 | animatorSet = new AnimatorSet(); 83 | 84 | ObjectAnimator largeX = ObjectAnimator.ofFloat(item, "ScaleX", 1f, scale); 85 | ObjectAnimator largeY = ObjectAnimator.ofFloat(item, "ScaleY", 1f, 86 | scale); 87 | animatorSet.setDuration(durationLarge); 88 | animatorSet.setInterpolator(interpolatorLarge); 89 | animatorSet.play(largeX).with(largeY); 90 | animatorSet.start(); 91 | 92 | oldView = item; 93 | } 94 | 95 | /** 96 | * 还原指定view 97 | * @param item 98 | */ 99 | public void scaleToNormal(final View item) { 100 | DebugLog.e("FocusScaleUtils__scaleToNormal=="+item); 101 | if (animatorSet == null || item == null) { 102 | return; 103 | } 104 | 105 | if (animatorSet.isRunning()) { 106 | animatorSet.cancel(); 107 | } 108 | 109 | AnimatorSet animatorSetForNormal = new AnimatorSet(); 110 | 111 | ObjectAnimator oa = ObjectAnimator.ofFloat(item, "ScaleX", 1f); 112 | ObjectAnimator oa2 = ObjectAnimator.ofFloat(item, "ScaleY", 1f); 113 | animatorSetForNormal.setDuration(durationSmall); 114 | animatorSetForNormal.setInterpolator(interpolatorSmall); 115 | animatorSetForNormal.play(oa).with(oa2); 116 | animatorSetForNormal.addListener(new Animator.AnimatorListener() { 117 | @Override 118 | public void onAnimationStart(Animator animation) { 119 | animatorSetMap.put(item,animation); 120 | } 121 | 122 | @Override 123 | public void onAnimationEnd(Animator animation) { 124 | animatorSetMap.remove(item); 125 | } 126 | 127 | @Override 128 | public void onAnimationCancel(Animator animation) { 129 | animatorSetMap.remove(item); 130 | } 131 | 132 | @Override 133 | public void onAnimationRepeat(Animator animation) { 134 | 135 | } 136 | }); 137 | animatorSetForNormal.start(); 138 | oldView = null; 139 | } 140 | 141 | /** 142 | * 还原上一次放大的view 143 | */ 144 | public void scaleToNormal() { 145 | scaleToNormal(oldView); 146 | } 147 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/bring/BringToFrontHelper.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.bring; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | /** 7 | * Created by JJYYZJY on 2017/9/6. 8 | */ 9 | public class BringToFrontHelper { 10 | 11 | private int mFocusChildIndex; 12 | 13 | public void bringChildToFront(ViewGroup viewGroup, View childView){ 14 | mFocusChildIndex = viewGroup.indexOfChild(childView); 15 | if(mFocusChildIndex != -1){ 16 | viewGroup.postInvalidate(); 17 | } 18 | } 19 | 20 | public int getChildDrawingOrder(int childCount, int i){ 21 | if(mFocusChildIndex != -1){ 22 | if(i == childCount-1){ 23 | if(mFocusChildIndex > childCount-1){ 24 | mFocusChildIndex = childCount-1; 25 | } 26 | return mFocusChildIndex; 27 | } 28 | if(i == mFocusChildIndex){ 29 | 30 | return childCount -1; 31 | } 32 | } 33 | if(childCount <= i){ 34 | i = childCount-1; 35 | } 36 | return i; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/bring/BringToFrontLinear.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.bring; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.LinearLayout; 7 | 8 | /** 9 | * Created by JJYYZJY on 2017/9/7. 10 | */ 11 | public class BringToFrontLinear extends LinearLayout { 12 | 13 | public BringToFrontLinear(Context context) { 14 | super(context); 15 | mInit(); 16 | } 17 | 18 | public BringToFrontLinear(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | mInit(); 21 | } 22 | 23 | public BringToFrontLinear(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | mInit(); 26 | } 27 | 28 | public BringToFrontLinear(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 29 | super(context, attrs, defStyleAttr, defStyleRes); 30 | mInit(); 31 | } 32 | 33 | private BringToFrontHelper bringToFrontHelper; 34 | 35 | private void mInit() { 36 | setWillNotDraw(true); 37 | setChildrenDrawingOrderEnabled(true); 38 | bringToFrontHelper = new BringToFrontHelper(); 39 | } 40 | 41 | @Override 42 | protected int getChildDrawingOrder(int childCount, int i) { 43 | return bringToFrontHelper.getChildDrawingOrder(childCount,i); 44 | } 45 | 46 | @Override 47 | public void bringChildToFront(View child) { 48 | bringToFrontHelper.bringChildToFront(this,child); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/bring/BringToFrontLinearLayout.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.bring; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | 7 | /** 8 | * 将持有焦点的view的父控件移到视图最上方 9 | * Created by JJYYZJY on 2017/9/7. 10 | */ 11 | public class BringToFrontLinearLayout extends BringToFrontLinear { 12 | public BringToFrontLinearLayout(Context context) { 13 | super(context); 14 | } 15 | 16 | public BringToFrontLinearLayout(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | } 19 | 20 | public BringToFrontLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { 21 | super(context, attrs, defStyleAttr); 22 | } 23 | 24 | public BringToFrontLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 25 | super(context, attrs, defStyleAttr, defStyleRes); 26 | } 27 | 28 | @Override 29 | public void requestChildFocus(View child, View focused) { 30 | child.bringToFront(); 31 | super.requestChildFocus(child, focused); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/bring/BringToFrontRelative.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.bring; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.RelativeLayout; 7 | 8 | /** 9 | * Created by JJYYZJY on 2017/9/7. 10 | */ 11 | public class BringToFrontRelative extends RelativeLayout { 12 | public BringToFrontRelative(Context context) { 13 | super(context); 14 | mInit(); 15 | } 16 | 17 | public BringToFrontRelative(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | mInit(); 20 | } 21 | 22 | public BringToFrontRelative(Context context, AttributeSet attrs, int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | mInit(); 25 | } 26 | 27 | public BringToFrontRelative(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 28 | super(context, attrs, defStyleAttr, defStyleRes); 29 | mInit(); 30 | } 31 | 32 | private BringToFrontHelper bringToFrontHelper; 33 | 34 | private void mInit() { 35 | setWillNotDraw(true); 36 | setChildrenDrawingOrderEnabled(true); 37 | bringToFrontHelper = new BringToFrontHelper(); 38 | } 39 | 40 | 41 | @Override 42 | protected int getChildDrawingOrder(int childCount, int i) { 43 | return bringToFrontHelper.getChildDrawingOrder(childCount,i); 44 | } 45 | 46 | @Override 47 | public void bringChildToFront(View child) { 48 | bringToFrontHelper.bringChildToFront(this,child); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/bring/BringToFrontRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.bring; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | 7 | /** 8 | * 将持有焦点的view的父控件移到视图最上方 9 | * Created by JJYYZJY on 2017/5/5. 10 | */ 11 | public class BringToFrontRelativeLayout extends BringToFrontRelative { 12 | public BringToFrontRelativeLayout(Context context) { 13 | super(context); 14 | } 15 | 16 | public BringToFrontRelativeLayout(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | } 19 | 20 | public BringToFrontRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 21 | super(context, attrs, defStyleAttr); 22 | } 23 | 24 | public BringToFrontRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 25 | super(context, attrs, defStyleAttr, defStyleRes); 26 | } 27 | 28 | @Override 29 | public void requestChildFocus(View child, View focused) { 30 | child.bringToFront(); 31 | super.requestChildFocus(child, focused); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/BaseBuildLayoutAdapter.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | /** 7 | * 动态布局——布局中item适配器 8 | * Created by ZJY on 2016/5/3. 9 | */ 10 | public abstract class BaseBuildLayoutAdapter { 11 | 12 | private Parameter parameter; 13 | 14 | public abstract int[] getLayout(int position); 15 | 16 | public abstract int getCount(); 17 | 18 | public abstract Object getItem(int position); 19 | 20 | public abstract long getItemId(int position); 21 | 22 | public int getItemViewType(int position){ 23 | return 0; 24 | } 25 | 26 | public abstract View getView(int position, View contentView, 27 | ViewGroup parent); 28 | 29 | /** 30 | * Set some parameters 31 | * @param parameter 32 | */ 33 | public void setParameter(Parameter parameter){ 34 | this.parameter = parameter; 35 | } 36 | 37 | /** 38 | * Get some parameters 39 | */ 40 | public Parameter getParameter(){ 41 | return parameter; 42 | } 43 | 44 | private BuildAdapterDataObserver observer; 45 | 46 | public void registerAdapterDataObserver(BuildAdapterDataObserver observer) { 47 | this.observer = observer; 48 | } 49 | 50 | public void unregisterAdapterDataObserver(BuildAdapterDataObserver observer) { 51 | this.observer = null; 52 | } 53 | 54 | public final void notifyDataSetChanged() { 55 | observer.onChanged(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/BuildAdapterDataObserver.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | /** 4 | * Created by JJYYZJY on 2017/9/19. 5 | */ 6 | public abstract class BuildAdapterDataObserver { 7 | 8 | public void onChanged() { 9 | 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/BuildItemPoll.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.util.SparseArray; 5 | import android.util.SparseIntArray; 6 | import android.view.View; 7 | 8 | import java.util.ArrayList; 9 | 10 | /** 11 | * Created by JJYYZJY on 2017/9/18. 12 | */ 13 | public class BuildItemPoll/* extends RecyclerView.RecycledViewPool */{ 14 | 15 | private SparseArray> mScrap = 16 | new SparseArray>(); 17 | private SparseIntArray mMaxScrap = new SparseIntArray(); 18 | private static final int DEFAULT_MAX_SCRAP = 5; 19 | 20 | public void clear() { 21 | mScrap.clear(); 22 | } 23 | 24 | public void setMaxRecycledViews(int viewType, int max) { 25 | mMaxScrap.put(viewType, max); 26 | final ArrayList scrapHeap = mScrap.get(viewType); 27 | if (scrapHeap != null) { 28 | while (scrapHeap.size() > max) { 29 | scrapHeap.remove(scrapHeap.size() - 1); 30 | } 31 | } 32 | } 33 | 34 | public View getBuildItemView(int viewType) { 35 | final ArrayList scrapHeap = mScrap.get(viewType); 36 | if (scrapHeap != null && !scrapHeap.isEmpty()) { 37 | final int index = scrapHeap.size() - 1; 38 | final View scrap = scrapHeap.get(index); 39 | scrapHeap.remove(index); 40 | return scrap; 41 | } 42 | return null; 43 | } 44 | 45 | public void putBuildItemView(int viewType, View scrap) { 46 | getScrapHeapForType(viewType).add(scrap); 47 | } 48 | 49 | private ArrayList getScrapHeapForType(int viewType) { 50 | ArrayList scrap = mScrap.get(viewType); 51 | if (scrap == null) { 52 | scrap = new ArrayList<>(); 53 | mScrap.put(viewType, scrap); 54 | } 55 | return scrap; 56 | } 57 | 58 | int size() { 59 | int count = 0; 60 | for (int i = 0; i < mScrap.size(); i ++) { 61 | ArrayList items = mScrap.valueAt(i); 62 | if (items != null) { 63 | count += items.size(); 64 | } 65 | } 66 | return count; 67 | } 68 | 69 | public void putAllItemView(@NonNull BuildItemPoll poll){ 70 | 71 | for (int i = 0; i < poll.mScrap.size(); i ++) { 72 | final int key = poll.mScrap.keyAt(i); 73 | mScrap.append(key,poll.mScrap.get(key)); 74 | } 75 | 76 | poll.clear(); 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/BuildLayout.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.RelativeLayout; 9 | 10 | import com.fb.jjyyzjy.buildlayoutdemo.R; 11 | import com.fb.jjyyzjy.buildlayoutdemo.utils.DebugLog; 12 | import com.fb.jjyyzjy.buildlayoutdemo.view.bring.BringToFrontRelative; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by JJYYZJY on 2017/9/18. 19 | */ 20 | public class BuildLayout extends BringToFrontRelative { 21 | protected static final String TAG = "MyBuildLayout"; 22 | protected static final boolean DEBUG_CAN = false; 23 | private List childViews; 24 | protected View[][] cViewTree = null; 25 | private View defaultGetFocusView;//默认获取焦点的View 26 | private View defaultGetFocusViewLB;//默认获取焦点的View 27 | private View defaultGetFocusViewRT;//默认获取焦点的View 28 | private View defaultGetFocusViewRB;//默认获取焦点的View 29 | private BaseBuildLayoutAdapter buildLayoutAdapter; 30 | private boolean isDealWithFocus;//是否处理焦点,使其左右不能离开布局 31 | /** 32 | * 单元格宽 33 | */ 34 | protected int itemWidth = (int) getResources().getDimension(R.dimen.px270); 35 | /** 36 | * 单元格高 37 | */ 38 | protected int itemHeight = (int) getResources().getDimension(R.dimen.px180); 39 | /** 40 | * 列数 41 | */ 42 | private int hSize; 43 | /** 44 | * 行数 45 | */ 46 | private int vSize; 47 | /** 48 | * 单元格间隔 49 | */ 50 | protected int dividing; 51 | 52 | private int widthP; 53 | private int heightP; 54 | 55 | public BuildLayout(Context context) { 56 | this(context,null); 57 | } 58 | 59 | public BuildLayout(Context context, AttributeSet attrs) { 60 | this(context, attrs,0); 61 | } 62 | 63 | public BuildLayout(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | if (attrs != null) { 66 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyBuildLayout); 67 | this.hSize = typedArray.getInteger(R.styleable.MyBuildLayout_myHSize, 0); 68 | this.vSize = typedArray.getInteger(R.styleable.MyBuildLayout_myVSize, 0); 69 | this.itemWidth = (int) typedArray.getDimension(R.styleable.MyBuildLayout_myItemWidth, this.itemWidth); 70 | this.itemHeight = (int) typedArray.getDimension(R.styleable.MyBuildLayout_myItemHeight, this.itemHeight); 71 | this.dividing = (int) typedArray.getDimension(R.styleable.MyBuildLayout_myDividing, 0); 72 | typedArray.recycle(); 73 | } 74 | init(); 75 | } 76 | 77 | private void init() { 78 | childViews = new ArrayList<>(); 79 | } 80 | 81 | @Override 82 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 83 | // super.onMeasure(widthMeasureSpec, heightMeasureSpec); 84 | for (int i = 0; i < childViews.size(); i++) { 85 | measureChild(childViews.get(i), widthMeasureSpec, heightMeasureSpec); 86 | } 87 | if (hSize == 0 || vSize == 0) { 88 | hSize = 5; 89 | vSize = 3; 90 | } 91 | if (itemWidth == 0 || itemHeight == 0) { 92 | itemWidth = (int) getResources().getDimension(R.dimen.px270); 93 | itemHeight = (int) getResources().getDimension(R.dimen.px180); 94 | } 95 | 96 | widthP = hSize * itemWidth + (hSize - 1) * dividing; 97 | heightP = vSize * itemHeight + (vSize - 1) * dividing; 98 | setMeasuredDimension(widthP, heightP); 99 | } 100 | 101 | @Override 102 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 103 | // super.onLayout(changed, l, t, r, b); 104 | if (changed) { 105 | mySetChildLayout(); 106 | }else { 107 | int cCount = childViews.size(); 108 | int cWidth = 0; 109 | int cHeight = 0; 110 | for (int i = 0; i < cCount; i++) { 111 | View childView = childViews.get(i); 112 | cWidth = childView.getMeasuredWidth(); 113 | cHeight = childView.getMeasuredHeight(); 114 | int cl = 0, ct = 0, cr = 0, cb = 0; 115 | cl = childView.getLeft(); 116 | ct = childView.getTop(); 117 | cr = cl + cWidth; 118 | cb = cHeight + ct; 119 | childView.layout(cl, ct, cr, cb); 120 | } 121 | } 122 | } 123 | 124 | private void mySetChildLayout() { 125 | // MyLog.v(TAG, "onLayout==getChildCount=="+getChildCount()); 126 | // MyLog.v(TAG, "onLayout==childViews.size()=="+childViews.size()); 127 | if (DEBUG_CAN) DebugLog.i("vSize = " + vSize + ",hSize = " + hSize); 128 | cViewTree = new View[vSize][hSize]; 129 | for (int i = 0; i < childViews.size(); i++) { 130 | 131 | int[] childLayout = buildLayoutAdapter.getLayout(i); 132 | if (childLayout.length < 4) { 133 | continue; 134 | } 135 | 136 | int v_r = childLayout[0] * itemWidth + childLayout[0] * dividing; 137 | int v_t = childLayout[1] * itemHeight + childLayout[1] * dividing; 138 | int v_w = childLayout[2] * itemWidth + (childLayout[2] - 1) * dividing; 139 | int v_h = childLayout[3] * itemHeight + (childLayout[3] - 1) * dividing; 140 | 141 | if (v_r == 0 && v_t == 0) { 142 | defaultGetFocusView = childViews.get(i); 143 | } 144 | 145 | if (v_r == 0 && (v_t + v_h) == heightP) { 146 | defaultGetFocusViewLB = childViews.get(i); 147 | } 148 | 149 | if ((v_r + v_w) == widthP && v_t == 0) { 150 | defaultGetFocusViewRT = childViews.get(i); 151 | } 152 | 153 | if ((v_r + v_w) == widthP && (v_t + v_h) == heightP) { 154 | defaultGetFocusViewRB = childViews.get(i); 155 | } 156 | 157 | childViews.get(i).layout(v_r, 158 | v_t, 159 | v_r + v_w, 160 | v_t + v_h); 161 | 162 | /* 163 | * 修改 164 | * 将child填充进其其所包含的所有坐标点,使查找更方便快速 165 | * @data 2017/4/19 166 | * @author zjy 167 | */ 168 | int atArrayH = childLayout[1]; 169 | int atArrayV = childLayout[0]; 170 | int childW = childLayout[2]; 171 | int childH = childLayout[3]; 172 | if (DEBUG_CAN) 173 | DebugLog.i("add child to array : atArrayH=" + atArrayH + ", atArrayV=" + atArrayV + ", childW=" + childW + ", childH=" + childH); 174 | if (atArrayH < cViewTree.length && atArrayV < cViewTree[atArrayH].length) { 175 | while (childH > 0) { 176 | int childWCopy = childW; 177 | childH--; 178 | while (childWCopy > 0) { 179 | --childWCopy; 180 | if ((atArrayH + childH) < cViewTree.length && (atArrayV + childWCopy) < cViewTree[atArrayH + childH].length) { 181 | cViewTree[atArrayH + childH][atArrayV + childWCopy] = childViews.get(i); 182 | if (DEBUG_CAN) 183 | DebugLog.i("add child to array : array[0]=" + (atArrayH + childH) + ", array[1]=" + (atArrayV + childWCopy)); 184 | } else { 185 | /*if (DEBUG_CAN) */ 186 | DebugLog.w("err ArrayIndexOutOfBounds add child to array : array[0]=" + (atArrayH + childH) + ", array[1]=" + (atArrayV + childWCopy)); 187 | } 188 | } 189 | } 190 | } 191 | 192 | childViews.get(i).setTag(childLayout); 193 | // MyLog.v("v_r", v_r+""); 194 | // MyLog.v("v_t", v_t+""); 195 | // MyLog.v("v_w", v_w+""); 196 | // MyLog.v("v_h", v_h+""); 197 | } 198 | if (isDealWithFocus) 199 | bindFocusSearchForBound(); 200 | } 201 | 202 | private void initAdapter() { 203 | initItem(); 204 | } 205 | 206 | BuildItemPoll mItemPoll = new BuildItemPoll(); 207 | private void initItem() { 208 | mItemPoll.putAllItemView(poll); 209 | View focusViewFlag = findFocus(); 210 | final int childCount = buildLayoutAdapter.getCount(); 211 | View cacheItem; 212 | View newItem; 213 | int itemType; 214 | for (int i = 0; i < childCount; i++) { 215 | int[] childLayout = buildLayoutAdapter.getLayout(i); 216 | if (childLayout.length < 4) { 217 | continue; 218 | } 219 | itemType = buildLayoutAdapter.getItemViewType(i); 220 | cacheItem = getCacheViewForType(itemType); 221 | 222 | int v_w = childLayout[2] * itemWidth + (childLayout[2] - 1) * dividing; 223 | int v_h = childLayout[3] * itemHeight + (childLayout[3] - 1) * dividing; 224 | if (cacheItem != null) { 225 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) cacheItem.getLayoutParams(); 226 | lp.width = v_w; 227 | lp.height = v_h; 228 | cacheItem.setLayoutParams(lp); 229 | int wMeasure = View.MeasureSpec.makeMeasureSpec(v_w, View.MeasureSpec.EXACTLY); 230 | int hMeasure = View.MeasureSpec.makeMeasureSpec(v_h, View.MeasureSpec.EXACTLY); 231 | cacheItem.setTag(R.id.key_tag_build_item_w, v_w); 232 | cacheItem.setTag(R.id.key_tag_build_item_h, v_h); 233 | cacheItem.setRight(cacheItem.getLeft() + v_w); 234 | cacheItem.setBottom(cacheItem.getTop() + v_h); 235 | cacheItem.measure(wMeasure, hMeasure); 236 | if (cacheItem instanceof ViewGroup) { 237 | ViewGroup viewGroup = (ViewGroup) cacheItem; 238 | View viewChild; 239 | int widthChild; 240 | int heightChild; 241 | for (int j = 0; j < viewGroup.getChildCount(); j++) { 242 | viewChild = viewGroup.getChildAt(j); 243 | widthChild = viewChild.getMeasuredWidth(); 244 | heightChild = viewChild.getMeasuredHeight(); 245 | viewChild.setRight(viewChild.getLeft() + widthChild); 246 | viewChild.setBottom(viewChild.getTop() + heightChild); 247 | } 248 | } 249 | } 250 | newItem = buildLayoutAdapter.getView(i, cacheItem, this); 251 | DebugLog.i(TAG + " cacheItem : " + cacheItem + ",newItem : " + newItem); 252 | if (cacheItem == null || (newItem != null && newItem != cacheItem)) { 253 | if (DEBUG_CAN) DebugLog.i("initItem newItem != cacheItem"); 254 | RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(v_w, v_h); 255 | newItem.setLayoutParams(lp); 256 | if (newItem.getParent() != null) { 257 | ((ViewGroup) newItem.getParent()).removeView(newItem); 258 | } 259 | // addView(newItem); 260 | } 261 | if (newItem != null) { 262 | if (DEBUG_CAN) DebugLog.i("newItem != null"); 263 | poll.putBuildItemView(itemType,newItem); 264 | if (childViews.contains(newItem)){ 265 | int index = childViews.indexOf(newItem); 266 | if (index != i){ 267 | childViews.remove(index); 268 | childViews.add(i,newItem); 269 | } 270 | }else { 271 | if (i < childViews.size()){ 272 | removeView(childViews.remove(i)); 273 | childViews.add(i,newItem); 274 | }else { 275 | childViews.add(newItem); 276 | } 277 | addView(newItem); 278 | } 279 | } 280 | 281 | } 282 | if (DEBUG_CAN) { 283 | DebugLog.i(TAG + " mItemPoll.size() : " + mItemPoll.size() + "mItemPoll : " + mItemPoll.toString()); 284 | if (itemPoll != null) { 285 | DebugLog.i(TAG + " itemPoll.size() : " + itemPoll.size() + "itemPoll : " + itemPoll.toString()); 286 | } 287 | DebugLog.i(TAG + " childViews.size : " + childViews.size() + "childViews : " + childViews.toString()); 288 | DebugLog.i(TAG + " getChildCount : " + getChildCount()); 289 | for (int i = 0; i < getChildCount(); i++) { 290 | DebugLog.i(TAG + "getChild : " + i + " " + getChildAt(i)); 291 | } 292 | } 293 | 294 | for (int i = childViews.size() - 1; i >= childCount; i--) { 295 | childViews.remove(i); 296 | } 297 | if (DEBUG_CAN) 298 | DebugLog.i(TAG + " childViews.size2 : " + childViews.size() + "childViews2 : " + childViews.toString()); 299 | if (DEBUG_CAN) DebugLog.i(TAG + "getChildCount2 : " + getChildCount()); 300 | View viewC; 301 | for (int n = 0; n < getChildCount(); n++) { 302 | viewC = getChildAt(n); 303 | if (!childViews.contains(viewC)) { 304 | removeView(viewC); 305 | n--; 306 | } 307 | } 308 | if (DEBUG_CAN) 309 | DebugLog.i(TAG + " childViews.size() : " + childViews.size() + "childViews : " + childViews.toString()); 310 | DebugLog.i(TAG + " getChildCount : " + getChildCount()); 311 | 312 | mySetChildLayout(); 313 | bindEvent(); 314 | //解决数据更新由于控件复用导致带有焦点的item位置变化而焦点框位置未更新 315 | if (focusViewFlag != null) { 316 | if (childViews != null && childViews.size() > 0) { 317 | if (focusViewFlag != childViews.get(0)) { 318 | childViews.get(0).requestFocus(); 319 | } else { 320 | if (childViews.size() > 1) { 321 | childViews.get(1).requestFocus(); 322 | } 323 | } 324 | if (childViews.contains(focusViewFlag)) { 325 | focusViewFlag.requestFocus(); 326 | } 327 | } 328 | } 329 | mItemPoll.clear(); 330 | } 331 | 332 | BuildItemPoll poll = new BuildItemPoll(); 333 | private View getCacheViewForType(int itemType) { 334 | View cacheItem = mItemPoll.getBuildItemView(itemType); 335 | if (cacheItem == null && itemPoll != null){ 336 | cacheItem = itemPoll.getBuildItemView(itemType); 337 | } 338 | return cacheItem; 339 | } 340 | 341 | private void bindEvent() { 342 | View child; 343 | for (int i = 0; i < getChildCount(); i++) { 344 | child = getChildAt(i); 345 | if (child != null) { 346 | child.setOnFocusChangeListener(new View.OnFocusChangeListener() { 347 | @Override 348 | public void onFocusChange(final View child, boolean focus) { 349 | if (onChildSelectListener != null) { 350 | onChildSelectListener.onChildSelect(child, focus); 351 | } 352 | if (onChildFocusChangeListener != null) { 353 | onChildFocusChangeListener.onChildFocus(child, focus, false, 0F); 354 | } 355 | } 356 | }); 357 | if (onChildClickListener != null) { 358 | child.setOnClickListener(new View.OnClickListener() { 359 | @Override 360 | public void onClick(View child) { 361 | onChildClickListener.onChildClick(child); 362 | 363 | } 364 | }); 365 | } 366 | } 367 | } 368 | } 369 | 370 | private void bindFocusSearchForBound() { 371 | View view; 372 | for (int i = 0; i < childViews.size(); i++) { 373 | view = childViews.get(i); 374 | FocusSearchUtils.unBindFocus(view); 375 | } 376 | if (cViewTree == null) { 377 | return; 378 | } 379 | if (cViewTree.length > 0) { 380 | for (int i = 0; i < cViewTree.length; i++) { 381 | if (cViewTree[i].length > 0) { 382 | FocusSearchUtils.bindFocusSearch(cViewTree[i][0], cViewTree[i][cViewTree[i].length - 1]); 383 | } 384 | } 385 | } 386 | } 387 | 388 | 389 | 390 | /** 391 | * 设置单元格宽高 392 | * 393 | * @param itemWidth 394 | * @param itemHeight 395 | */ 396 | public void mySetItemSize(int itemWidth, int itemHeight) { 397 | this.itemWidth = itemWidth; 398 | this.itemHeight = itemHeight; 399 | setItemLayout(); 400 | invalidate(); 401 | } 402 | 403 | private void setItemLayout() { 404 | View child; 405 | for (int i = 0; i < childViews.size(); i++) { 406 | child = childViews.get(i); 407 | if (child == null) { 408 | continue; 409 | } 410 | int[] childLayout = buildLayoutAdapter.getLayout(i); 411 | if (childLayout.length < 4) { 412 | continue; 413 | } 414 | int v_w = childLayout[2] * itemWidth + (childLayout[2] - 1) * dividing; 415 | int v_h = childLayout[3] * itemHeight + (childLayout[3] - 1) * dividing; 416 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) child.getLayoutParams(); 417 | if (lp == null) { 418 | lp = new RelativeLayout.LayoutParams(v_w, v_h); 419 | } else { 420 | lp.width = v_w; 421 | lp.height = v_h; 422 | } 423 | child.setLayoutParams(lp); 424 | } 425 | } 426 | 427 | /** 428 | * 设置控件尺寸 429 | * 430 | * @param hSize 列数 431 | * @param vSize 行数 432 | */ 433 | public void mySetLayoutSize(int hSize, int vSize) { 434 | if (DEBUG_CAN) DebugLog.i("mySetLayoutSize [hSize=" + hSize + ",vSize=" + vSize + "]"); 435 | this.hSize = hSize; 436 | this.vSize = vSize; 437 | invalidate(); 438 | } 439 | 440 | /** 441 | * 设置间隔 442 | */ 443 | public void mySetDividing(int dividing) { 444 | this.dividing = dividing; 445 | invalidate(); 446 | } 447 | 448 | /** 449 | * 处理焦点左右移动 450 | * 451 | * @param isDealWithFocus 452 | */ 453 | public void setIsDealWithFocus(boolean isDealWithFocus) { 454 | this.isDealWithFocus = isDealWithFocus; 455 | if (isDealWithFocus) { 456 | bindFocusSearchForBound(); 457 | } 458 | } 459 | 460 | public boolean getIsDealWithFocus() { 461 | return isDealWithFocus; 462 | } 463 | 464 | /** 465 | * 获得默认获取焦点的View 466 | * 467 | * @return 468 | */ 469 | public View myGetDefaultFocusView() { 470 | return defaultGetFocusView; 471 | } 472 | 473 | /** 474 | * 获得默认获取焦点的View 475 | * 476 | * @return 477 | */ 478 | public View myGetDefaultFocusViewLB() { 479 | return defaultGetFocusViewLB; 480 | } 481 | 482 | /** 483 | * 获得默认获取焦点的View 484 | * 485 | * @return 486 | */ 487 | public View myGetDefaultFocusViewRT() { 488 | return defaultGetFocusViewRT; 489 | } 490 | 491 | /** 492 | * 获得默认获取焦点的View 493 | * 494 | * @return 495 | */ 496 | public View myGetDefaultFocusViewRB() { 497 | return defaultGetFocusViewRB; 498 | } 499 | 500 | private final BuildAdapterDataObserver observer = new BuildAdapterDataObserver() { 501 | @Override 502 | public void onChanged() { 503 | super.onChanged(); 504 | initAdapter(); 505 | } 506 | }; 507 | 508 | /** 509 | * 设置布局适配器 510 | * 511 | * @param buildLayoutAdapter 512 | */ 513 | public void setAdapter(BaseBuildLayoutAdapter buildLayoutAdapter) { 514 | if (DEBUG_CAN) DebugLog.i("setAdapter buildLayoutAdapter : " + buildLayoutAdapter); 515 | if (this.buildLayoutAdapter != null){ 516 | this.buildLayoutAdapter.unregisterAdapterDataObserver(observer); 517 | } 518 | if (buildLayoutAdapter != null) { 519 | this.buildLayoutAdapter = buildLayoutAdapter; 520 | buildLayoutAdapter.registerAdapterDataObserver(observer); 521 | initAdapter(); 522 | } 523 | } 524 | 525 | public BaseBuildLayoutAdapter getAdapter(){ 526 | return buildLayoutAdapter; 527 | } 528 | 529 | private static BuildItemPoll itemPoll; 530 | public void setBuildItemPoll(BuildItemPoll poll){ 531 | itemPoll = poll; 532 | } 533 | 534 | public BuildItemPoll getItemPoll(){ 535 | return itemPoll; 536 | } 537 | 538 | public void detach(){ 539 | if (itemPoll != null){ 540 | itemPoll.putAllItemView(poll); 541 | childViews.clear(); 542 | removeAllViews(); 543 | } 544 | } 545 | 546 | private OnChildSelectListener onChildSelectListener; 547 | private OnChildClickListener onChildClickListener; 548 | private OnChildFocusChangeListener onChildFocusChangeListener; 549 | 550 | public void setOnChildSelectListener(OnChildSelectListener myListener) { 551 | this.onChildSelectListener = myListener; 552 | } 553 | 554 | public void setOnChildClickListener(OnChildClickListener myListener) { 555 | this.onChildClickListener = myListener; 556 | } 557 | 558 | public void setOnChildFocusChangeListener(OnChildFocusChangeListener myListener) { 559 | this.onChildFocusChangeListener = myListener; 560 | } 561 | 562 | public interface OnChildSelectListener { 563 | public void onChildSelect(View child, boolean isSelect); 564 | } 565 | 566 | public interface OnChildClickListener { 567 | public void onChildClick(View child); 568 | } 569 | 570 | public interface OnChildFocusChangeListener { 571 | public void onChildFocus(View child, boolean isFocus, boolean scalable, float scale); 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/BuildLayoutUtils.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import com.fb.jjyyzjy.buildlayoutdemo.utils.CastUtils; 4 | 5 | /** 6 | * 自定义动态布局,辅助工具类 7 | * Created by ZJY on 2016/6/18. 8 | */ 9 | public class BuildLayoutUtils { 10 | 11 | /** 12 | * 根据服务器返回数据转换成对应宽高尺寸 13 | * @param sSize 14 | * @param mW 15 | * @param mH 16 | * @return 17 | */ 18 | public static int[] getBuildLayoutSize(String sSize, int mW, int mH){ 19 | int[] size = new int[2]; 20 | if (sSize != null){ 21 | String[] sizeLayout = sSize.split("\\*"); 22 | if (sizeLayout.length >= 2){ 23 | size[0] = CastUtils.getIntForString(sizeLayout[0],mW); 24 | size[1] = CastUtils.getIntForString(sizeLayout[1],mH); 25 | }else { 26 | size[0] = mW; 27 | size[1] = mH; 28 | } 29 | }else { 30 | size[0] = mW; 31 | size[1] = mH; 32 | } 33 | return size; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/FocusSearchUtils.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.view.KeyEvent; 4 | import android.view.View; 5 | 6 | import com.fb.jjyyzjy.buildlayoutdemo.R; 7 | import com.fb.jjyyzjy.buildlayoutdemo.utils.DebugLog; 8 | 9 | 10 | /** 11 | * Created by JJYYZJY on 2017/5/3. 12 | */ 13 | public class FocusSearchUtils { 14 | 15 | private static final boolean DEBUG = true; 16 | 17 | public static void bindFocusSearch(View viewLeft, View viewRight){ 18 | if (DEBUG) DebugLog.i("bindFocusSearch viewLeft : "+viewLeft+", viewRight : "+viewRight); 19 | if (viewLeft == null || viewRight == null){ 20 | return; 21 | } 22 | viewLeft.setTag(R.id.key_tag_focus_has_next_left,viewRight); 23 | viewRight.setTag(R.id.key_tag_focus_has_next_right,viewLeft); 24 | } 25 | 26 | public static void bindFocusSearch(View[] viewLeft, View[] viewRight){ 27 | if (DEBUG) DebugLog.i("bindFocusSearch viewLeft[] : "+viewLeft+", viewRight[] : "+viewRight); 28 | if (viewLeft == null || viewRight == null){ 29 | return; 30 | } 31 | int count = viewLeft.length > viewRight.length ? viewLeft.length : viewRight.length; 32 | for (int i = 0 ; i < count ; i++){ 33 | bindFocusSearch(viewLeft[i >= viewLeft.length ? viewLeft.length-1 : i] 34 | ,viewRight[i >= viewRight.length ? viewRight.length-1 : i]); 35 | } 36 | } 37 | 38 | public static void unBindFocus(View viewLeft, View viewRight){ 39 | if (viewLeft == null || viewRight == null){ 40 | return; 41 | } 42 | viewLeft.setTag(R.id.key_tag_focus_has_next_left,null); 43 | viewRight.setTag(R.id.key_tag_focus_has_next_right,null); 44 | } 45 | 46 | public static void unBindFocus(View view){ 47 | if (view == null){ 48 | return; 49 | } 50 | view.setTag(R.id.key_tag_focus_has_next_left,null); 51 | view.setTag(R.id.key_tag_focus_has_next_right,null); 52 | } 53 | 54 | public static boolean requestFocusSearch(View focus, int direction){ 55 | if (DEBUG) DebugLog.i("requestFocusSearch direction : "+direction+",focus : "+focus); 56 | if (focus == null){ 57 | return false; 58 | } 59 | if (direction == View.FOCUS_LEFT){ 60 | final View tag = (View) focus.getTag(R.id.key_tag_focus_has_next_left); 61 | if (tag != null){ 62 | final View focusNext = tag.focusSearch(View.FOCUS_UP); 63 | if (DEBUG) DebugLog.i("FOCUS_LEFT focusNext : "+focusNext + "\n tag : "+tag); 64 | if (focusNext != null && focusNext != tag){ 65 | boolean is = focusNext.requestFocus(); 66 | DebugLog.w("FOCUS_LEFT isRequest : " + is); 67 | } 68 | return true; 69 | } 70 | }else if (direction == View.FOCUS_RIGHT){ 71 | final View tag = (View) focus.getTag(R.id.key_tag_focus_has_next_right); 72 | if (tag != null){ 73 | final View focusNext = tag.focusSearch(View.FOCUS_DOWN); 74 | if (DEBUG) DebugLog.i("FOCUS_RIGHT focusNext : "+focusNext + "\n tag : "+tag); 75 | if (focusNext != null && focusNext != tag) { 76 | boolean is = focusNext.requestFocus(); 77 | DebugLog.w("FOCUS_RIGHT isRequest : " + is); 78 | } 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | 85 | public static boolean dispatchKeyEvent(View mRootView, KeyEvent event){ 86 | if (DEBUG) DebugLog.i("dispatchKeyEvent event : " + event); 87 | if (event.getAction() == KeyEvent.ACTION_DOWN){ 88 | if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT){ 89 | final View focus = mRootView != null ? mRootView.findFocus() : null; 90 | if (focus != null){ 91 | if (requestFocusSearch(focus, View.FOCUS_LEFT)){ 92 | return true; 93 | } 94 | } 95 | }else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT){ 96 | final View focus = mRootView != null ? mRootView.findFocus() : null; 97 | if (focus != null){ 98 | if (requestFocusSearch(focus, View.FOCUS_RIGHT)){ 99 | return true; 100 | } 101 | } 102 | } 103 | } 104 | return false; 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/MyBuildLayout.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.KeyEvent; 6 | import android.view.View; 7 | import android.view.WindowManager; 8 | 9 | import com.fb.jjyyzjy.buildlayoutdemo.utils.DebugLog; 10 | 11 | /** 12 | * 动态表格布局 13 | * @author JJYYZJY 14 | */ 15 | public class MyBuildLayout extends BuildLayout { 16 | 17 | public MyBuildLayout(Context context) { 18 | super(context); 19 | init(); 20 | } 21 | 22 | public MyBuildLayout(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | init(); 25 | } 26 | 27 | public MyBuildLayout(Context context, AttributeSet attrs, int defStyle) { 28 | super(context, attrs, defStyle); 29 | init(); 30 | } 31 | 32 | private void init() { 33 | WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 34 | windowWidth = windowManager.getDefaultDisplay().getWidth(); 35 | } 36 | 37 | /** 38 | * 焦点出界监听 39 | */ 40 | private OnDismissFocusListener2 dismissListener; 41 | private OnOutOfBoundListener outOfBoundListener; 42 | private int windowWidth; 43 | 44 | @Override 45 | public boolean dispatchKeyEvent(KeyEvent event) { 46 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 47 | if (DEBUG_CAN) DebugLog.i("dispatchKeyEvent ACTION_DOWN"); 48 | View focus = findFocus(); 49 | switch (event.getKeyCode()) { 50 | case KeyEvent.KEYCODE_DPAD_DOWN: 51 | if (focus.getBottom() == getHeight()) { 52 | if (dismissListener != null) { 53 | if (dismissListener.onDismissForBot(this)) { 54 | return true; 55 | } 56 | } 57 | } else { 58 | if (focusSearchWithTag(focus.getTag(), FOCUS_DOWN)) { 59 | return true; 60 | } 61 | } 62 | break; 63 | case KeyEvent.KEYCODE_DPAD_UP: 64 | if (focus.getTop() == 0) { 65 | if (dismissListener != null) { 66 | if (dismissListener.onDismissForTop(this)) { 67 | return true; 68 | } 69 | } 70 | } else { 71 | if (focusSearchWithTag(focus.getTag(), FOCUS_UP)) { 72 | return true; 73 | } 74 | } 75 | break; 76 | case KeyEvent.KEYCODE_DPAD_LEFT: 77 | if (focus.getLeft() == 0) { 78 | if (dismissListener != null) { 79 | if (dismissListener.onDismissForLeft(this)) { 80 | return true; 81 | } 82 | } 83 | } else { 84 | if (outOfBoundListener != null) { 85 | int[] location = new int[2]; 86 | focus.getLocationInWindow(location); 87 | // MyLog.v("focus.getLeft()",""+focus.getLeft()); 88 | // MyLog.v("location[0]",""+location[0]); 89 | // MyLog.v("itemWidth",""+itemWidth); 90 | // MyLog.v("dividing",""+dividing); 91 | if (focus.getLeft() > location[0] && location[0] < itemWidth + dividing) { 92 | if (DEBUG_CAN) DebugLog.i("outOfBoundListener ok"); 93 | outOfBoundListener.onOutForLeft(focus); 94 | } 95 | } 96 | 97 | if (focusSearchWithTag(focus.getTag(), FOCUS_LEFT)) { 98 | return true; 99 | } 100 | 101 | } 102 | break; 103 | case KeyEvent.KEYCODE_DPAD_RIGHT: 104 | if (focus.getRight() == getWidth()) { 105 | if (dismissListener != null) { 106 | if (dismissListener.onDismissForRight(this)) { 107 | return true; 108 | } 109 | } 110 | } else { 111 | if (outOfBoundListener != null) { 112 | 113 | int[] location = new int[2]; 114 | focus.getLocationInWindow(location); 115 | 116 | // MyLog.v("location[0] + itemWidth + dividing + itemWidth",""+ (location[0] + itemWidth + dividing + itemWidth)); 117 | 118 | if (location[0] + itemWidth + dividing + itemWidth > windowWidth) { 119 | if (DEBUG_CAN) DebugLog.i("outOfBoundListener ok"); 120 | outOfBoundListener.onOutForRight(focus); 121 | } 122 | } 123 | 124 | if (focusSearchWithTag(focus.getTag(), FOCUS_RIGHT)) { 125 | return true; 126 | } 127 | 128 | } 129 | break; 130 | } 131 | } 132 | return super.dispatchKeyEvent(event); 133 | } 134 | 135 | private boolean focusSearchWithTag(Object tag, int direction) { 136 | if (DEBUG_CAN) DebugLog.i(TAG + " focusSearchWithTag"); 137 | if (tag instanceof int[]) { 138 | int[] layout = (int[]) tag; 139 | if (DEBUG_CAN) 140 | DebugLog.i(TAG + " focusSearchWithTag tag(layout) : [" + layout[0] + "," + layout[1] + "," + layout[2] + "," + layout[3] + "] direction : " + direction); 141 | switch (direction) { 142 | case FOCUS_UP: 143 | if (layout[2] >= 1) { 144 | View nextFocus = findViewWithFuzzyLayout(layout[0], layout[1] - 1, layout[3], FOCUS_UP); 145 | if (nextFocus != null) { 146 | return nextFocus.requestFocus(); 147 | } 148 | } 149 | break; 150 | case FOCUS_DOWN: 151 | if (layout[2] >= 1) { 152 | View nextFocus = findViewWithFuzzyLayout(layout[0], layout[1] + layout[3], layout[2], FOCUS_DOWN); 153 | if (nextFocus != null) { 154 | return nextFocus.requestFocus(); 155 | } 156 | } 157 | break; 158 | case FOCUS_LEFT: 159 | if (layout[3] >= 1) { 160 | View nextFocus = findViewWithFuzzyLayout(layout[0] - 1, layout[1], layout[3], FOCUS_LEFT); 161 | if (nextFocus != null) { 162 | return nextFocus.requestFocus(); 163 | } 164 | } 165 | break; 166 | case FOCUS_RIGHT: 167 | if (layout[3] >= 1) { 168 | View nextFocus = findViewWithFuzzyLayout(layout[0] + layout[2], layout[1], layout[3], FOCUS_RIGHT); 169 | if (nextFocus != null) { 170 | return nextFocus.requestFocus(); 171 | } 172 | } 173 | break; 174 | } 175 | } 176 | if (DEBUG_CAN) DebugLog.i(TAG + " focusSearchWithTag false"); 177 | return false; 178 | } 179 | 180 | private View findViewWithFuzzyLayout(int x, int y, int range, int direction) { 181 | if (DEBUG_CAN) 182 | DebugLog.i(TAG + " findViewWithFuzzyLayout [x=" + x + ",y=" + y + ",range=" + range + ",direction=" + direction + "]"); 183 | if (cViewTree == null || x < 0 || y < 0 || y >= cViewTree.length || x >= cViewTree[y].length) { 184 | if (DEBUG_CAN) DebugLog.w(TAG + " findViewWithFuzzyLayout parameter err"); 185 | return null; 186 | } 187 | View nextView = null; 188 | switch (direction) { 189 | case FOCUS_UP: 190 | while (nextView == null && y >= 0) { 191 | int flag = 0; 192 | while (nextView == null && range > flag) { 193 | nextView = cViewTree[y][x + flag]; 194 | if (DEBUG_CAN) { 195 | if (nextView != null) 196 | DebugLog.i("cViewTree[" + y + "][" + (x + flag) + "]"); 197 | } 198 | flag++; 199 | } 200 | y--; 201 | } 202 | break; 203 | case FOCUS_DOWN: 204 | while (nextView == null && y >= 0 && y < cViewTree.length) { 205 | int flag = 0; 206 | while (nextView == null && range > flag) { 207 | nextView = cViewTree[y][x + flag]; 208 | if (DEBUG_CAN) { 209 | if (nextView != null) 210 | DebugLog.i("cViewTree[" + y + "][" + (x + flag) + "]"); 211 | } 212 | flag++; 213 | } 214 | y++; 215 | } 216 | break; 217 | case FOCUS_LEFT: 218 | while (nextView == null && x >= 0) { 219 | int flag = 0; 220 | while (nextView == null && range > flag) { 221 | nextView = cViewTree[y + flag][x]; 222 | if (DEBUG_CAN) { 223 | if (nextView != null) 224 | DebugLog.i("cViewTree[" + (y + flag) + "][" + x + "]"); 225 | } 226 | flag++; 227 | } 228 | x--; 229 | } 230 | break; 231 | case FOCUS_RIGHT: 232 | while (nextView == null && x >= 0 && x < cViewTree[y].length) { 233 | int flag = 0; 234 | while (nextView == null && range > flag) { 235 | nextView = cViewTree[y + flag][x]; 236 | if (DEBUG_CAN) { 237 | if (nextView != null) 238 | DebugLog.i("cViewTree[" + (y + flag) + "][" + x + "]"); 239 | } 240 | flag++; 241 | } 242 | x++; 243 | } 244 | break; 245 | } 246 | if (DEBUG_CAN) DebugLog.i(TAG + " findViewWithFuzzyLayout nextView : " + nextView); 247 | return nextView; 248 | } 249 | 250 | 251 | public View[] getLeftChildTree() { 252 | if (cViewTree != null) { 253 | View[] views = new View[cViewTree.length]; 254 | for (int i = 0; i < cViewTree.length; i++) { 255 | if (cViewTree[i].length > 0) { 256 | views[i] = cViewTree[i][0]; 257 | } 258 | } 259 | return views; 260 | } 261 | return null; 262 | } 263 | 264 | public View[] getRightChildTree() { 265 | DebugLog.i("getRightChildTree cViewTree : " + cViewTree.toString() + ",cViewTree.length : " + cViewTree.length); 266 | if (cViewTree != null) { 267 | View[] views = new View[cViewTree.length]; 268 | for (int i = 0; i < cViewTree.length; i++) { 269 | if (cViewTree[i].length > 0) { 270 | views[i] = cViewTree[i][cViewTree[i].length - 1]; 271 | DebugLog.i("views[" + i + "] : " + views[i]); 272 | } 273 | } 274 | return views; 275 | } 276 | return null; 277 | } 278 | 279 | public void setOnDismissFocusListener(OnDismissFocusListener2 dismissListener) { 280 | this.dismissListener = dismissListener; 281 | } 282 | 283 | public void setOnOutOfBoundListener(OnOutOfBoundListener outOfBoundListener) { 284 | this.outOfBoundListener = outOfBoundListener; 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/OnDismissFocusListener2.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * 失去焦点2_带返回_true为截断事件 7 | * Created by ZJY on 2016/4/26. 8 | */ 9 | public interface OnDismissFocusListener2 { 10 | public boolean onDismissForTop(View v); 11 | public boolean onDismissForBot(View v); 12 | public boolean onDismissForLeft(View v); 13 | public boolean onDismissForRight(View v); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/OnOutOfBoundListener.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * 宽度超多视图窗口的view焦点超过视图监听 7 | * Created by ZJY on 2016/5/12. 8 | */ 9 | public interface OnOutOfBoundListener { 10 | void onOutForRight(View v); 11 | void onOutForLeft(View v); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/fb/jjyyzjy/buildlayoutdemo/view/buildview/Parameter.java: -------------------------------------------------------------------------------- 1 | package com.fb.jjyyzjy.buildlayoutdemo.view.buildview; 2 | 3 | /** 4 | * Created by JJYYZJY on 2017/6/6. 5 | */ 6 | public class Parameter { 7 | 8 | private int pageIndex; 9 | private int itemPosition; 10 | 11 | public int getPageIndex() { 12 | return pageIndex; 13 | } 14 | 15 | public void setPageIndex(int pageIndex) { 16 | this.pageIndex = pageIndex; 17 | } 18 | 19 | public int getItemPosition() { 20 | return itemPosition; 21 | } 22 | 23 | public void setItemPosition(int itemPosition) { 24 | this.itemPosition = itemPosition; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "Parameter{" + 30 | "pageIndex=" + pageIndex + 31 | ", itemPosition=" + itemPosition + 32 | '}'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 30 | 31 |