├── .gitignore
├── README.md
├── build.gradle
├── example
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── owen
│ │ └── tvrecyclerview
│ │ └── example
│ │ ├── LayoutAdapter.java
│ │ ├── LayoutFragment.java
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-hdpi
│ ├── ic_grid.png
│ ├── ic_launcher.png
│ ├── ic_list.png
│ ├── ic_spannable.png
│ ├── ic_spannable_selected.png
│ └── ic_staggered.png
│ ├── drawable
│ ├── divider.xml
│ ├── item_background.xml
│ └── selector_ic_spannable.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── item.xml
│ ├── layout_grid.xml
│ ├── layout_list.xml
│ ├── layout_spannable_grid.xml
│ └── layout_staggered_grid.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── img_grid.png
├── img_list.png
├── img_spannable.png
└── img_staggered.png
├── library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── owen
│ │ └── tvrecyclerview
│ │ ├── BaseLayoutManager.java
│ │ ├── ItemEntries.java
│ │ ├── Lanes.java
│ │ ├── TwoWayLayoutManager.java
│ │ └── widget
│ │ ├── DividerItemDecoration.java
│ │ ├── GridLayoutManager.java
│ │ ├── ItemSpacingOffsets.java
│ │ ├── ListLayoutManager.java
│ │ ├── SpacingItemDecoration.java
│ │ ├── SpannableGridLayoutManager.java
│ │ ├── StaggeredGridLayoutManager.java
│ │ └── TvRecyclerView.java
│ └── res
│ └── values
│ ├── attrs.xml
│ └── strings.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 欢迎使用 TvRecyclerView
2 |
3 | 首先感谢lucasr开发出杰出的作品[TwoWayView](https://github.com/lucasr/twoway-view),**TvRecyclerView**就是在[TwoWayView](https://github.com/lucasr/twoway-view)的基础上进行的延伸,即:
4 |
5 | > * 修复了一些小bug
6 | > * 针对TV端的特性进行了适配与开发
7 |
8 | ### 效果
9 |
10 | 
11 |
12 | 
13 |
14 | 
15 |
16 | 
17 |
18 | ### Android Studio 集成
19 |
20 | ```java
21 | compile 'com.tv.boost:tv-recyclerview:1.0.1'
22 | ```
23 |
24 | ### 特性
25 |
26 | - [x] 支持焦点快速移动
27 |
28 | - [x] 支持Item选中放大时不被叠压(无需手动调用bringChildToFront())
29 |
30 | - [x] 支持横/竖排列
31 | ```java
32 | android:orientation="horizontal"
33 | ```
34 |
35 | - [x] 支持布局指定LayoutManager
36 | ```java
37 | app:tv_layoutManager="SpannableGridLayoutManager"
38 | ```
39 |
40 | - [x] 支持设置选中Item边缘距离/居中
41 | ```java
42 | setSelectedItemAtCentered(boolean isCentered)
43 | setSelectedItemOffset(int offsetStart, int offsetEnd)
44 | ```
45 |
46 | - [x] 支持设置横竖间距
47 | ```java
48 | setSpacingWithMargins(int verticalSpacing, int horizontalSpacing)
49 | ```
50 |
51 | - [x] Item监听回调
52 | ```java
53 | mRecyclerView.setOnItemListener(new TvRecyclerView.OnItemListener() {
54 | @Override
55 | public void onItemPreSelected(TvRecyclerView parent, View itemView, int position) {
56 |
57 | }
58 |
59 | @Override
60 | public void onItemSelected(TvRecyclerView parent, View itemView, int position) {
61 |
62 | }
63 |
64 | @Override
65 | public void onItemClick(TvRecyclerView parent, View itemView, int position) {
66 |
67 | }
68 | });
69 | ```
70 |
71 |
72 | ### 更详细的使用请见exmaple
73 |
74 | ------
75 |
76 |
77 | 作者 [owen](https://github.com/zhousuqiang)
78 |
--------------------------------------------------------------------------------
/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 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.2'
9 |
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' //自动化maven打包插件
11 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' //自动上传至Bintray平台插件
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/example/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.owen.tvrecyclerview.example"
9 | minSdkVersion 14
10 | targetSdkVersion 23
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 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.4.0'
26 | compile project(':library')
27 | }
28 |
--------------------------------------------------------------------------------
/example/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/owen/Documents/Developer_Tool/adt-bundle-mac-x86_64-20131030/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 |
--------------------------------------------------------------------------------
/example/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/example/src/main/java/com/owen/tvrecyclerview/example/LayoutAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.example;
18 |
19 | import android.content.Context;
20 | import android.support.v7.widget.RecyclerView;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.widget.TextView;
25 |
26 | import com.owen.tvrecyclerview.TwoWayLayoutManager;
27 | import com.owen.tvrecyclerview.widget.SpannableGridLayoutManager;
28 | import com.owen.tvrecyclerview.widget.StaggeredGridLayoutManager;
29 | import com.owen.tvrecyclerview.widget.TvRecyclerView;
30 |
31 | import java.util.ArrayList;
32 | import java.util.List;
33 |
34 | public class LayoutAdapter extends RecyclerView.Adapter {
35 | private static final int COUNT = 50;
36 |
37 | private final Context mContext;
38 | private final TvRecyclerView mRecyclerView;
39 | private final List mItems;
40 | private final int mLayoutId;
41 | private int mCurrentItemId = 0;
42 |
43 | public static class SimpleViewHolder extends RecyclerView.ViewHolder {
44 | public final TextView title;
45 |
46 | public SimpleViewHolder(View view) {
47 | super(view);
48 | title = (TextView) view.findViewById(R.id.title);
49 | }
50 | }
51 |
52 | public LayoutAdapter(Context context, TvRecyclerView recyclerView, int layoutId) {
53 | mContext = context;
54 | mItems = new ArrayList(COUNT);
55 | for (int i = 0; i < COUNT; i++) {
56 | addItem(i);
57 | }
58 |
59 | mRecyclerView = recyclerView;
60 | mLayoutId = layoutId;
61 | }
62 |
63 | public void addItem(int position) {
64 | final int id = mCurrentItemId++;
65 | mItems.add(position, id);
66 | notifyItemInserted(position);
67 | }
68 |
69 | public void removeItem(int position) {
70 | mItems.remove(position);
71 | notifyItemRemoved(position);
72 | }
73 |
74 | @Override
75 | public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
76 | final View view = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
77 | return new SimpleViewHolder(view);
78 | }
79 |
80 | @Override
81 | public void onBindViewHolder(SimpleViewHolder holder, final int position) {
82 | holder.title.setText(mItems.get(position).toString());
83 |
84 | boolean isVertical = (mRecyclerView.getOrientation() == TwoWayLayoutManager.Orientation.VERTICAL);
85 | final View itemView = holder.itemView;
86 | final int itemId = mItems.get(position);
87 |
88 | if (mLayoutId == R.layout.layout_staggered_grid) {
89 | final int dimenId;
90 | if (itemId % 3 == 0) {
91 | dimenId = R.dimen.staggered_child_medium;
92 | } else if (itemId % 5 == 0) {
93 | dimenId = R.dimen.staggered_child_large;
94 | } else if (itemId % 7 == 0) {
95 | dimenId = R.dimen.staggered_child_xlarge;
96 | } else {
97 | dimenId = R.dimen.staggered_child_small;
98 | }
99 |
100 | final int span;
101 | if (itemId == 2) {
102 | span = 2;
103 | } else {
104 | span = 1;
105 | }
106 |
107 | final int size = mContext.getResources().getDimensionPixelSize(dimenId);
108 |
109 | final StaggeredGridLayoutManager.LayoutParams lp =
110 | (StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams();
111 |
112 | if (!isVertical) {
113 | lp.span = span;
114 | lp.width = size;
115 | itemView.setLayoutParams(lp);
116 | } else {
117 | lp.span = span;
118 | lp.height = size;
119 | itemView.setLayoutParams(lp);
120 | }
121 | } else if (mLayoutId == R.layout.layout_spannable_grid) {
122 | final SpannableGridLayoutManager.LayoutParams lp =
123 | (SpannableGridLayoutManager.LayoutParams) itemView.getLayoutParams();
124 |
125 | final int span1 = (itemId == 0 || itemId == 6 || itemId == 13 || itemId == 5 ? 2 : 1);
126 | final int span2 = (itemId == 0 || itemId == 6 || itemId == 13 ? 2 : itemId == 5 ? 4 : 1);
127 |
128 | final int colSpan = (isVertical ? span2 : span1);
129 | final int rowSpan = (isVertical ? span1 : span2);
130 | if (lp.rowSpan != rowSpan || lp.colSpan != colSpan) {
131 | lp.rowSpan = rowSpan;
132 | lp.colSpan = colSpan;
133 |
134 | itemView.setLayoutParams(lp);
135 | }
136 | }
137 | }
138 |
139 | @Override
140 | public int getItemCount() {
141 | return mItems.size();
142 | }
143 |
144 | public interface OnItemSelectedListenner {
145 | void onSelected(View view, int positin);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/example/src/main/java/com/owen/tvrecyclerview/example/LayoutFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.example;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 | import android.support.v4.app.Fragment;
22 | import android.support.v7.widget.RecyclerView;
23 | import android.view.Gravity;
24 | import android.view.LayoutInflater;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 | import android.widget.TextView;
28 | import android.widget.Toast;
29 |
30 | import com.owen.tvrecyclerview.widget.TvRecyclerView;
31 |
32 | import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
33 | import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
34 | import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
35 |
36 | public class LayoutFragment extends Fragment {
37 | private static final String ARG_LAYOUT_ID = "layout_id";
38 |
39 | private TvRecyclerView mRecyclerView;
40 | private TextView mPositionText;
41 | private TextView mCountText;
42 | private TextView mStateText;
43 | private Toast mToast;
44 |
45 | private int mLayoutId;
46 |
47 | public static LayoutFragment newInstance(int layoutId) {
48 | LayoutFragment fragment = new LayoutFragment();
49 |
50 | Bundle args = new Bundle();
51 | args.putInt(ARG_LAYOUT_ID, layoutId);
52 | fragment.setArguments(args);
53 |
54 | return fragment;
55 | }
56 |
57 | @Override
58 | public void onCreate(Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | mLayoutId = getArguments().getInt(ARG_LAYOUT_ID);
61 | }
62 |
63 | @Override
64 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
65 | Bundle savedInstanceState) {
66 | return inflater.inflate(mLayoutId, container, false);
67 | }
68 |
69 | @Override
70 | public void onViewCreated(View view, Bundle savedInstanceState) {
71 | super.onViewCreated(view, savedInstanceState);
72 |
73 | final Activity activity = getActivity();
74 |
75 | mToast = Toast.makeText(activity, "", Toast.LENGTH_SHORT);
76 | mToast.setGravity(Gravity.CENTER, 0, 0);
77 |
78 | View btnView = view.findViewById(R.id.btn1);
79 | if(null != btnView) {
80 | btnView.setOnClickListener(new View.OnClickListener() {
81 | @Override
82 | public void onClick(View v) {
83 | mRecyclerView.smoothScrollToPosition(15);
84 | }
85 | });
86 | }
87 |
88 | mRecyclerView = (TvRecyclerView) view.findViewById(R.id.list);
89 | mRecyclerView.setHasFixedSize(true);
90 | mRecyclerView.setLongClickable(true);
91 |
92 | mPositionText = (TextView) view.getRootView().findViewById(R.id.position);
93 | mCountText = (TextView) view.getRootView().findViewById(R.id.count);
94 |
95 | mStateText = (TextView) view.getRootView().findViewById(R.id.state);
96 | updateState(SCROLL_STATE_IDLE);
97 |
98 | mRecyclerView.setOnItemListener(new TvRecyclerView.OnItemListener() {
99 | @Override
100 | public void onItemPreSelected(TvRecyclerView parent, View itemView, int position) {
101 | itemView.animate().scaleX(1f).scaleY(1f).setDuration(300).start();
102 | }
103 |
104 | @Override
105 | public void onItemSelected(TvRecyclerView parent, View itemView, int position) {
106 | itemView.animate().scaleX(1.4f).scaleY(1.4f).setDuration(300).start();
107 | }
108 |
109 | @Override
110 | public void onItemClick(TvRecyclerView parent, View itemView, int position) {
111 | mToast.setText("onItemClick::"+position);
112 | mToast.show();
113 | }
114 | });
115 |
116 | mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
117 | @Override
118 | public void onScrollStateChanged(RecyclerView recyclerView, int scrollState) {
119 | updateState(scrollState);
120 | }
121 |
122 | @Override
123 | public void onScrolled(RecyclerView recyclerView, int i, int i2) {
124 | mPositionText.setText("First: " + mRecyclerView.getFirstVisiblePosition());
125 | mCountText.setText("Count: " + mRecyclerView.getChildCount());
126 | }
127 | });
128 |
129 |
130 | // final Drawable divider = getResources().getDrawable(R.drawable.divider);
131 | // mRecyclerView.addItemDecoration(new DividerItemDecoration(divider));
132 | // mRecyclerView.addItemDecoration(new SpacingItemDecoration(20, 20));
133 | // 通过Margins来设置布局的横纵间距(与addItemDecoration()方法可二选一)
134 | // 推荐使用此方法
135 | mRecyclerView.setSpacingWithMargins(18, 18);
136 |
137 |
138 | // 设置选中的Item距离开始或结束的偏移量(与setSelectedItemAtCentered()方法二选一)
139 | // mRecyclerView.setSelectedItemOffset(120, 120);
140 | // 设置选中的Item居中(与setSelectedItemOffset()方法二选一)
141 | mRecyclerView.setSelectedItemAtCentered(true);
142 |
143 | mRecyclerView.setAdapter(new LayoutAdapter(activity, mRecyclerView, mLayoutId));
144 | }
145 |
146 | private void updateState(int scrollState) {
147 | String stateName = "Undefined";
148 | switch(scrollState) {
149 | case SCROLL_STATE_IDLE:
150 | stateName = "Idle";
151 | break;
152 |
153 | case SCROLL_STATE_DRAGGING:
154 | stateName = "Dragging";
155 | break;
156 |
157 | case SCROLL_STATE_SETTLING:
158 | stateName = "Flinging";
159 | break;
160 | }
161 |
162 | mStateText.setText(stateName);
163 | }
164 |
165 | public int getLayoutId() {
166 | return getArguments().getInt(ARG_LAYOUT_ID);
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/example/src/main/java/com/owen/tvrecyclerview/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.example;
18 |
19 | import android.os.Bundle;
20 | import android.support.v4.app.FragmentTransaction;
21 | import android.support.v7.app.ActionBar;
22 | import android.support.v7.app.AppCompatActivity;
23 | import android.util.Log;
24 | import android.view.KeyEvent;
25 |
26 | public class MainActivity extends AppCompatActivity {
27 | private final String LOGTAG = MainActivity.class.getSimpleName();
28 | private final String ARG_SELECTED_LAYOUT_ID = "selectedLayoutId";
29 |
30 | private final int DEFAULT_LAYOUT = R.layout.layout_list;
31 |
32 | private int mSelectedLayoutId;
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 |
39 | ActionBar actionBar = getSupportActionBar();
40 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
41 | actionBar.setDisplayShowTitleEnabled(false);
42 | actionBar.setDisplayShowHomeEnabled(false);
43 |
44 | mSelectedLayoutId = DEFAULT_LAYOUT;
45 | if (savedInstanceState != null) {
46 | mSelectedLayoutId = savedInstanceState.getInt(ARG_SELECTED_LAYOUT_ID);
47 | }
48 |
49 | addLayoutTab(
50 | actionBar, R.layout.layout_list, R.drawable.ic_list, "list");
51 | addLayoutTab(
52 | actionBar, R.layout.layout_grid, R.drawable.ic_grid, "grid");
53 | addLayoutTab(
54 | actionBar, R.layout.layout_staggered_grid, R.drawable.ic_staggered, "staggered");
55 | addLayoutTab(
56 | actionBar, R.layout.layout_spannable_grid, R.drawable.selector_ic_spannable, "spannable");
57 | }
58 |
59 | @Override
60 | protected void onSaveInstanceState(Bundle outState) {
61 | super.onSaveInstanceState(outState);
62 | outState.putInt(ARG_SELECTED_LAYOUT_ID, mSelectedLayoutId);
63 | }
64 |
65 | private void addLayoutTab(ActionBar actionBar, int layoutId, int iconId, String tag) {
66 | ActionBar.Tab tab = actionBar.newTab()
67 | // .setText(tag)
68 | .setIcon(iconId)
69 | .setTabListener(new TabListener(layoutId, tag));
70 | actionBar.addTab(tab, layoutId == mSelectedLayoutId);
71 | }
72 |
73 | @Override
74 | public boolean dispatchKeyEvent(KeyEvent event) {
75 | return super.dispatchKeyEvent(event);
76 | }
77 |
78 | @Override
79 | public boolean onKeyDown(int keyCode, KeyEvent event) {
80 | Log.d(LOGTAG, "keyCode="+keyCode);
81 | return super.onKeyDown(keyCode, event);
82 | }
83 |
84 | public class TabListener implements ActionBar.TabListener {
85 | private LayoutFragment mFragment;
86 | private final int mLayoutId;
87 | private final String mTag;
88 |
89 | public TabListener(int layoutId, String tag) {
90 | mLayoutId = layoutId;
91 | mTag = tag;
92 | }
93 |
94 | @Override
95 | public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
96 | mFragment = (LayoutFragment) getSupportFragmentManager().findFragmentByTag(mTag);
97 | if (mFragment == null) {
98 | mFragment = (LayoutFragment) LayoutFragment.newInstance(mLayoutId);
99 | ft.add(R.id.content, mFragment, mTag);
100 | } else {
101 | ft.attach(mFragment);
102 | }
103 |
104 | mSelectedLayoutId = mFragment.getLayoutId();
105 | }
106 |
107 | @Override
108 | public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
109 | if (mFragment != null) {
110 | ft.detach(mFragment);
111 | }
112 | }
113 |
114 | @Override
115 | public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable-hdpi/ic_grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/drawable-hdpi/ic_grid.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable-hdpi/ic_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/drawable-hdpi/ic_list.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable-hdpi/ic_spannable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/drawable-hdpi/ic_spannable.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable-hdpi/ic_spannable_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/drawable-hdpi/ic_spannable_selected.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable-hdpi/ic_staggered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/drawable-hdpi/ic_staggered.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable/divider.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
19 |
20 |
21 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/item_background.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
21 |
22 |
24 |
25 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/selector_ic_spannable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
26 |
27 |
31 |
32 |
35 |
36 |
39 |
40 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/item.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/layout_grid.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/layout_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
23 |
24 |
28 |
29 |
34 |
35 |
44 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/layout_spannable_grid.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
25 |
30 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/example/src/main/res/layout/layout_staggered_grid.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/example/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/example/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | #3FBA91
20 |
21 | #DEDEDE
22 | #999999
23 |
24 | #666666
25 | #EEEEEE
26 | #CCCCCC
27 | #77CEEE
28 |
29 |
--------------------------------------------------------------------------------
/example/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | 16dp
20 | 16dp
21 |
22 | 32dp
23 | 16sp
24 |
25 | 1dp
26 |
27 | 120dp
28 | 230dp
29 | 310dp
30 | 400dp
31 |
32 |
33 |
--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | TvRecyclerViewSample
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
21 |
22 |
25 |
26 |
35 |
36 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/images/img_grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/images/img_grid.png
--------------------------------------------------------------------------------
/images/img_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/images/img_list.png
--------------------------------------------------------------------------------
/images/img_spannable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/images/img_spannable.png
--------------------------------------------------------------------------------
/images/img_staggered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FrozenFreeFall/TvRecyclerView/4a4e3bf9a43ed01f07d6e8098f562d55e3b32f22/images/img_staggered.png
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | gradle.properties
3 | bintray.gradle
4 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 23
10 | versionCode 2
11 | versionName "1.0.1"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | apply from: 'bintray.gradle'
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support:appcompat-v7:23.4.0'
26 | compile 'com.android.support:recyclerview-v7:23.4.0'
27 | }
28 |
--------------------------------------------------------------------------------
/library/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/owen/Documents/Developer_Tool/adt-bundle-mac-x86_64-20131030/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 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/BaseLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview;
18 |
19 | import android.content.Context;
20 | import android.graphics.Rect;
21 | import android.os.Parcel;
22 | import android.os.Parcelable;
23 | import android.support.v7.widget.RecyclerView;
24 | import android.support.v7.widget.RecyclerView.LayoutParams;
25 | import android.support.v7.widget.RecyclerView.Recycler;
26 | import android.support.v7.widget.RecyclerView.State;
27 | import android.util.AttributeSet;
28 | import android.view.View;
29 | import android.view.ViewGroup;
30 | import android.view.ViewGroup.MarginLayoutParams;
31 |
32 | import com.owen.tvrecyclerview.Lanes.LaneInfo;
33 |
34 |
35 | public abstract class BaseLayoutManager extends TwoWayLayoutManager {
36 | private static final String LOGTAG = BaseLayoutManager.class.getSimpleName();
37 |
38 | protected static class ItemEntry implements Parcelable {
39 | public int startLane;
40 | public int anchorLane;
41 |
42 | private int[] spanMargins;
43 |
44 | public ItemEntry(int startLane, int anchorLane) {
45 | this.startLane = startLane;
46 | this.anchorLane = anchorLane;
47 | }
48 |
49 | public ItemEntry(Parcel in) {
50 | startLane = in.readInt();
51 | anchorLane = in.readInt();
52 |
53 | final int marginCount = in.readInt();
54 | if (marginCount > 0) {
55 | spanMargins = new int[marginCount];
56 | for (int i = 0; i < marginCount; i++) {
57 | spanMargins[i] = in.readInt();
58 | }
59 | }
60 | }
61 |
62 | @Override
63 | public int describeContents() {
64 | return 0;
65 | }
66 |
67 | @Override
68 | public void writeToParcel(Parcel out, int flags) {
69 | out.writeInt(startLane);
70 | out.writeInt(anchorLane);
71 |
72 | final int marginCount = (spanMargins != null ? spanMargins.length : 0);
73 | out.writeInt(marginCount);
74 |
75 | for (int i = 0; i < marginCount; i++) {
76 | out.writeInt(spanMargins[i]);
77 | }
78 | }
79 |
80 | public void setLane(Lanes.LaneInfo laneInfo) {
81 | startLane = laneInfo.startLane;
82 | anchorLane = laneInfo.anchorLane;
83 | }
84 |
85 | public void invalidateLane() {
86 | startLane = Lanes.NO_LANE;
87 | anchorLane = Lanes.NO_LANE;
88 | spanMargins = null;
89 | }
90 |
91 | private boolean hasSpanMargins() {
92 | return (spanMargins != null);
93 | }
94 |
95 | private int getSpanMargin(int index) {
96 | if (spanMargins == null) {
97 | return 0;
98 | }
99 |
100 | return spanMargins[index];
101 | }
102 |
103 | private void setSpanMargin(int index, int margin, int span) {
104 | if (spanMargins == null) {
105 | spanMargins = new int[span];
106 | }
107 |
108 | spanMargins[index] = margin;
109 | }
110 |
111 | public static final Creator CREATOR
112 | = new Creator() {
113 | @Override
114 | public ItemEntry createFromParcel(Parcel in) {
115 | return new ItemEntry(in);
116 | }
117 |
118 | @Override
119 | public ItemEntry[] newArray(int size) {
120 | return new ItemEntry[size];
121 | }
122 | };
123 | }
124 |
125 | private enum UpdateOp {
126 | ADD,
127 | REMOVE,
128 | UPDATE,
129 | MOVE
130 | }
131 |
132 | private Lanes mLanes;
133 | private Lanes mLanesToRestore;
134 |
135 | private ItemEntries mItemEntries;
136 | private ItemEntries mItemEntriesToRestore;
137 |
138 | protected final Rect mChildFrame = new Rect();
139 | protected final Rect mTempRect = new Rect();
140 | protected final LaneInfo mTempLaneInfo = new LaneInfo();
141 |
142 | public BaseLayoutManager(Context context, AttributeSet attrs) {
143 | this(context, attrs, 0);
144 | }
145 |
146 | public BaseLayoutManager(Context context, AttributeSet attrs, int defStyle) {
147 | super(context, attrs, defStyle);
148 | }
149 |
150 | public BaseLayoutManager(Orientation orientation) {
151 | super(orientation);
152 | }
153 |
154 | protected void pushChildFrame(ItemEntry entry, Rect childFrame, int lane, int laneSpan,
155 | Direction direction) {
156 | final boolean shouldSetMargins = (direction == Direction.END &&
157 | entry != null && !entry.hasSpanMargins());
158 |
159 | for (int i = lane; i < lane + laneSpan; i++) {
160 | final int spanMargin;
161 | if (entry != null && direction != Direction.END) {
162 | spanMargin = entry.getSpanMargin(i - lane);
163 | } else {
164 | spanMargin = 0;
165 | }
166 |
167 | final int margin = mLanes.pushChildFrame(childFrame, i, spanMargin, direction);
168 | if (laneSpan > 1 && shouldSetMargins) {
169 | entry.setSpanMargin(i - lane, margin, laneSpan);
170 | }
171 | }
172 | }
173 |
174 | private void popChildFrame(ItemEntry entry, Rect childFrame, int lane, int laneSpan,
175 | Direction direction) {
176 | for (int i = lane; i < lane + laneSpan; i++) {
177 | final int spanMargin;
178 | if (entry != null && direction != Direction.END) {
179 | spanMargin = entry.getSpanMargin(i - lane);
180 | } else {
181 | spanMargin = 0;
182 | }
183 |
184 | mLanes.popChildFrame(childFrame, i, spanMargin, direction);
185 | }
186 | }
187 |
188 | void getDecoratedChildFrame(View child, Rect childFrame) {
189 | childFrame.left = getDecoratedLeft(child);
190 | childFrame.top = getDecoratedTop(child);
191 | childFrame.right = getDecoratedRight(child);
192 | childFrame.bottom = getDecoratedBottom(child);
193 | }
194 |
195 | public boolean isVertical() {
196 | return (getOrientation() == Orientation.VERTICAL);
197 | }
198 |
199 | public Lanes getLanes() {
200 | return mLanes;
201 | }
202 |
203 | protected void setItemEntryForPosition(int position, ItemEntry entry) {
204 | if (mItemEntries != null) {
205 | mItemEntries.putItemEntry(position, entry);
206 | }
207 | }
208 |
209 | protected ItemEntry getItemEntryForPosition(int position) {
210 | return (mItemEntries != null ? mItemEntries.getItemEntry(position) : null);
211 | }
212 |
213 | protected void clearItemEntries() {
214 | if (mItemEntries != null) {
215 | mItemEntries.clear();
216 | }
217 | }
218 |
219 | protected void invalidateItemLanesAfter(int position) {
220 | if (mItemEntries != null) {
221 | mItemEntries.invalidateItemLanesAfter(position);
222 | }
223 | }
224 |
225 | protected void offsetForAddition(int positionStart, int itemCount) {
226 | if (mItemEntries != null) {
227 | mItemEntries.offsetForAddition(positionStart, itemCount);
228 | }
229 | }
230 |
231 | protected void offsetForRemoval(int positionStart, int itemCount) {
232 | if (mItemEntries != null) {
233 | mItemEntries.offsetForRemoval(positionStart, itemCount);
234 | }
235 | }
236 |
237 | private void requestMoveLayout() {
238 | if (getPendingScrollPosition() != RecyclerView.NO_POSITION) {
239 | return;
240 | }
241 |
242 | final int position = getFirstVisiblePosition();
243 | final View firstChild = findViewByPosition(position);
244 | final int offset = (firstChild != null ? getChildStart(firstChild) : 0);
245 |
246 | setPendingScrollPositionWithOffset(position, offset);
247 | }
248 |
249 | private boolean canUseLanes(Lanes lanes) {
250 | if (lanes == null) {
251 | return false;
252 | }
253 |
254 | final int laneCount = getLaneCount();
255 | final int laneSize = Lanes.calculateLaneSize(this, laneCount);
256 |
257 | return (lanes.getOrientation() == getOrientation() &&
258 | lanes.getCount() == laneCount &&
259 | lanes.getLaneSize() == laneSize);
260 | }
261 |
262 | private boolean ensureLayoutState() {
263 | final int laneCount = getLaneCount();
264 | if (laneCount == 0 || getWidth() == 0 || getHeight() == 0 || canUseLanes(mLanes)) {
265 | return false;
266 | }
267 |
268 | final Lanes oldLanes = mLanes;
269 | mLanes = new Lanes(this, laneCount);
270 |
271 | requestMoveLayout();
272 |
273 | if (mItemEntries == null) {
274 | mItemEntries = new ItemEntries();
275 | }
276 |
277 | if (oldLanes != null && oldLanes.getOrientation() == mLanes.getOrientation() &&
278 | oldLanes.getLaneSize() == mLanes.getLaneSize()) {
279 | invalidateItemLanesAfter(0);
280 | } else {
281 | mItemEntries.clear();
282 | }
283 |
284 | return true;
285 | }
286 |
287 | private void handleUpdate(int positionStart, int itemCountOrToPosition, UpdateOp cmd) {
288 | invalidateItemLanesAfter(positionStart);
289 |
290 | switch (cmd) {
291 | case ADD:
292 | offsetForAddition(positionStart, itemCountOrToPosition);
293 | break;
294 |
295 | case REMOVE:
296 | offsetForRemoval(positionStart, itemCountOrToPosition);
297 | break;
298 |
299 | case MOVE:
300 | offsetForRemoval(positionStart, 1);
301 | offsetForAddition(itemCountOrToPosition, 1);
302 | break;
303 | }
304 |
305 | if (positionStart + itemCountOrToPosition <= getFirstVisiblePosition()) {
306 | return;
307 | }
308 |
309 | if (positionStart <= getLastVisiblePosition()) {
310 | requestLayout();
311 | }
312 | }
313 |
314 | @Override
315 | public void offsetChildrenHorizontal(int offset) {
316 | if (!isVertical()) {
317 | mLanes.offset(offset);
318 | }
319 |
320 | super.offsetChildrenHorizontal(offset);
321 | }
322 |
323 | @Override
324 | public void offsetChildrenVertical(int offset) {
325 | super.offsetChildrenVertical(offset);
326 |
327 | if (isVertical()) {
328 | mLanes.offset(offset);
329 | }
330 | }
331 |
332 | @Override
333 | public void onLayoutChildren(Recycler recycler, State state) {
334 | final boolean restoringLanes = (mLanesToRestore != null);
335 | if (restoringLanes) {
336 | mLanes = mLanesToRestore;
337 | mItemEntries = mItemEntriesToRestore;
338 |
339 | mLanesToRestore = null;
340 | mItemEntriesToRestore = null;
341 | }
342 |
343 | final boolean refreshingLanes = ensureLayoutState();
344 |
345 | // Still not able to create lanes, nothing we can do here,
346 | // just bail for now.
347 | if (mLanes == null) {
348 | return;
349 | }
350 |
351 | final int itemCount = state.getItemCount();
352 |
353 | if (mItemEntries != null) {
354 | mItemEntries.setAdapterSize(itemCount);
355 | }
356 |
357 | final int anchorItemPosition = getAnchorItemPosition(state);
358 |
359 | // Only move layout if we're not restoring a layout state.
360 | if (anchorItemPosition > 0 && (refreshingLanes || !restoringLanes)) {
361 | moveLayoutToPosition(anchorItemPosition, getPendingScrollOffset(), recycler, state);
362 | }
363 |
364 | mLanes.reset(Direction.START);
365 |
366 | super.onLayoutChildren(recycler, state);
367 | }
368 |
369 | @Override
370 | protected void onLayoutScrapList(Recycler recycler, State state) {
371 | mLanes.save();
372 | super.onLayoutScrapList(recycler, state);
373 | mLanes.restore();
374 | }
375 |
376 | @Override
377 | public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
378 | handleUpdate(positionStart, itemCount, UpdateOp.ADD);
379 | super.onItemsAdded(recyclerView, positionStart, itemCount);
380 | }
381 |
382 | @Override
383 | public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
384 | handleUpdate(positionStart, itemCount, UpdateOp.REMOVE);
385 | super.onItemsRemoved(recyclerView, positionStart, itemCount);
386 | }
387 |
388 | @Override
389 | public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
390 | handleUpdate(positionStart, itemCount, UpdateOp.UPDATE);
391 | super.onItemsUpdated(recyclerView, positionStart, itemCount);
392 | }
393 |
394 | @Override
395 | public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
396 | handleUpdate(from, to, UpdateOp.MOVE);
397 | super.onItemsMoved(recyclerView, from, to, itemCount);
398 | }
399 |
400 | @Override
401 | public void onItemsChanged(RecyclerView recyclerView) {
402 | clearItemEntries();
403 | super.onItemsChanged(recyclerView);
404 | }
405 |
406 | @Override
407 | public Parcelable onSaveInstanceState() {
408 | final Parcelable superState = super.onSaveInstanceState();
409 | final LanedSavedState state = new LanedSavedState(superState);
410 |
411 | final int laneCount = (mLanes != null ? mLanes.getCount() : 0);
412 | state.lanes = new Rect[laneCount];
413 | for (int i = 0; i < laneCount; i++) {
414 | final Rect laneRect = new Rect();
415 | mLanes.getLane(i, laneRect);
416 | state.lanes[i] = laneRect;
417 | }
418 |
419 | state.orientation = getOrientation();
420 | state.laneSize = (mLanes != null ? mLanes.getLaneSize() : 0);
421 | state.itemEntries = mItemEntries;
422 |
423 | return state;
424 | }
425 |
426 | @Override
427 | public void onRestoreInstanceState(Parcelable state) {
428 | final LanedSavedState ss = (LanedSavedState) state;
429 |
430 | if (ss.lanes != null && ss.laneSize > 0) {
431 | mLanesToRestore = new Lanes(this, ss.orientation, ss.lanes, ss.laneSize);
432 | mItemEntriesToRestore = ss.itemEntries;
433 | }
434 |
435 | super.onRestoreInstanceState(ss.getSuperState());
436 | }
437 |
438 | @Override
439 | protected boolean canAddMoreViews(Direction direction, int limit) {
440 | if (direction == Direction.START) {
441 | return (mLanes.getInnerStart() > limit);
442 | } else {
443 | return (mLanes.getInnerEnd() < limit);
444 | }
445 | }
446 |
447 | private int getWidthUsed(View child) {
448 | if (!isVertical()) {
449 | return 0;
450 | }
451 |
452 | final int size = getLanes().getLaneSize() * getLaneSpanForChild(child);
453 | return getWidth() - getPaddingLeft() - getPaddingRight() - size;
454 | }
455 |
456 | private int getHeightUsed(View child) {
457 | if (isVertical()) {
458 | return 0;
459 | }
460 |
461 | final int size = getLanes().getLaneSize() * getLaneSpanForChild(child);
462 | return getHeight() - getPaddingTop() - getPaddingBottom() - size;
463 | }
464 |
465 | protected void measureChildWithMargins(View child) {
466 | measureChildWithMargins(child, getWidthUsed(child), getHeightUsed(child));
467 | }
468 |
469 | @Override
470 | protected void measureChild(View child, Direction direction) {
471 | cacheChildLaneAndSpan(child, direction);
472 | measureChildWithMargins(child);
473 | }
474 |
475 | @Override
476 | protected void layoutChild(View child, Direction direction) {
477 |
478 | getLaneForChild(mTempLaneInfo, child, direction);
479 |
480 | mLanes.getChildFrame(mChildFrame, getDecoratedMeasuredWidth(child),
481 | getDecoratedMeasuredHeight(child), mTempLaneInfo, direction);
482 | final ItemEntry entry = cacheChildFrame(child, mChildFrame);
483 |
484 | layoutDecorated(child, mChildFrame.left, mChildFrame.top, mChildFrame.right,
485 | mChildFrame.bottom);
486 |
487 |
488 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
489 | if (!lp.isItemRemoved()) {
490 | pushChildFrame(entry, mChildFrame, mTempLaneInfo.startLane,
491 | getLaneSpanForChild(child), direction);
492 | }
493 | }
494 |
495 | @Override
496 | protected void detachChild(View child, Direction direction) {
497 | final int position = getPosition(child);
498 | getLaneForPosition(mTempLaneInfo, position, direction);
499 | getDecoratedChildFrame(child, mChildFrame);
500 |
501 | popChildFrame(getItemEntryForPosition(position), mChildFrame, mTempLaneInfo.startLane,
502 | getLaneSpanForChild(child), direction);
503 | }
504 |
505 | protected void getLaneForChild(LaneInfo outInfo, View child, Direction direction) {
506 | getLaneForPosition(outInfo, getPosition(child), direction);
507 | }
508 |
509 | public int getLaneSpanForChild(View child) {
510 | return 1;
511 | }
512 |
513 | public int getLaneSpanForPosition(int position) {
514 | return 1;
515 | }
516 |
517 | protected ItemEntry cacheChildLaneAndSpan(View child, Direction direction) {
518 | // Do nothing by default.
519 | return null;
520 | }
521 |
522 | protected ItemEntry cacheChildFrame(View child, Rect childFrame) {
523 | // Do nothing by default.
524 | return null;
525 | }
526 |
527 | // add by zhousuqiang
528 | private int mVerticalSpacingWithMargins = 0;
529 | private int mHorizontalSpacingWithMargins = 0;
530 | /**
531 | * add by zhousuqiang
532 | * 通过Margins来设置布局的横纵间距
533 | * @param verticalSpacing
534 | * @param horizontalSpacing
535 | */
536 | public void setSpacingWithMargins(int verticalSpacing, int horizontalSpacing) {
537 | this.mVerticalSpacingWithMargins = verticalSpacing;
538 | this.mHorizontalSpacingWithMargins = horizontalSpacing;
539 | }
540 |
541 | @Override
542 | public boolean checkLayoutParams(LayoutParams lp) {
543 | // add by zhousuqiang
544 | if(mVerticalSpacingWithMargins > 0 || mHorizontalSpacingWithMargins > 0) {
545 | lp.setMargins(
546 | mVerticalSpacingWithMargins/2,
547 | mHorizontalSpacingWithMargins/2,
548 | mVerticalSpacingWithMargins/2,
549 | mHorizontalSpacingWithMargins/2
550 | );
551 | }
552 |
553 | if (isVertical()) {
554 | return (lp.width == LayoutParams.MATCH_PARENT);
555 | } else {
556 | return (lp.height == LayoutParams.MATCH_PARENT);
557 | }
558 | }
559 |
560 | @Override
561 | public LayoutParams generateDefaultLayoutParams() {
562 | if (isVertical()) {
563 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
564 | } else {
565 | return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
566 | }
567 | }
568 |
569 | @Override
570 | public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
571 | final LayoutParams lanedLp = new LayoutParams((MarginLayoutParams) lp);
572 | if (isVertical()) {
573 | lanedLp.width = LayoutParams.MATCH_PARENT;
574 | lanedLp.height = lp.height;
575 | } else {
576 | lanedLp.width = lp.width;
577 | lanedLp.height = LayoutParams.MATCH_PARENT;
578 | }
579 |
580 | return lanedLp;
581 | }
582 |
583 | @Override
584 | public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
585 | return new LayoutParams(c, attrs);
586 | }
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 | public abstract int getLaneCount();
595 | public abstract void getLaneForPosition(LaneInfo outInfo, int position, Direction direction);
596 | protected abstract void moveLayoutToPosition(int position, int offset, Recycler recycler, State state);
597 |
598 | protected static class LanedSavedState extends SavedState {
599 | private Orientation orientation;
600 | private Rect[] lanes;
601 | private int laneSize;
602 | private ItemEntries itemEntries;
603 |
604 | protected LanedSavedState(Parcelable superState) {
605 | super(superState);
606 | }
607 |
608 | private LanedSavedState(Parcel in) {
609 | super(in);
610 |
611 | orientation = Orientation.values()[in.readInt()];
612 | laneSize = in.readInt();
613 |
614 | final int laneCount = in.readInt();
615 | if (laneCount > 0) {
616 | lanes = new Rect[laneCount];
617 | for (int i = 0; i < laneCount; i++) {
618 | final Rect lane = new Rect();
619 | lane.readFromParcel(in);
620 | lanes[i] = lane;
621 | }
622 | }
623 |
624 | final int itemEntriesCount = in.readInt();
625 | if (itemEntriesCount > 0) {
626 | itemEntries = new ItemEntries();
627 | for (int i = 0; i < itemEntriesCount; i++) {
628 | final ItemEntry entry = in.readParcelable(getClass().getClassLoader());
629 | itemEntries.restoreItemEntry(i, entry);
630 | }
631 | }
632 | }
633 |
634 | @Override
635 | public void writeToParcel(Parcel out, int flags) {
636 | super.writeToParcel(out, flags);
637 |
638 | out.writeInt(orientation.ordinal());
639 | out.writeInt(laneSize);
640 |
641 | final int laneCount = (lanes != null ? lanes.length : 0);
642 | out.writeInt(laneCount);
643 |
644 | for (int i = 0; i < laneCount; i++) {
645 | lanes[i].writeToParcel(out, Rect.PARCELABLE_WRITE_RETURN_VALUE);
646 | }
647 |
648 | final int itemEntriesCount = (itemEntries != null ? itemEntries.size() : 0);
649 | out.writeInt(itemEntriesCount);
650 |
651 | for (int i = 0; i < itemEntriesCount; i++) {
652 | out.writeParcelable(itemEntries.getItemEntry(i), flags);
653 | }
654 | }
655 |
656 | public static final Creator CREATOR
657 | = new Creator() {
658 | @Override
659 | public LanedSavedState createFromParcel(Parcel in) {
660 | return new LanedSavedState(in);
661 | }
662 |
663 | @Override
664 | public LanedSavedState[] newArray(int size) {
665 | return new LanedSavedState[size];
666 | }
667 | };
668 | }
669 |
670 | // @Override
671 | // public boolean onRequestChildFocus(RecyclerView parent, State state, View child, View focused) {
672 | // if(mRecyclerView instanceof TwoWayView) {
673 | // ((TwoWayView)mRecyclerView).smoothToCenter(getPosition(child));
674 | // }
675 | // return super.onRequestChildFocus(parent, state, child, focused);
676 | // }
677 | }
678 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/ItemEntries.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * This code is based on Android's StaggeredLayoutManager's
5 | * LazySpanLookup class.
6 | *
7 | * Copyright (C) 2014 The Android Open Source Project
8 | *
9 | * Licensed under the Apache License, Version 2.0 (the "License");
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | package com.owen.tvrecyclerview;
23 |
24 | import com.owen.tvrecyclerview.BaseLayoutManager.ItemEntry;
25 |
26 | import java.util.Arrays;
27 |
28 | public class ItemEntries {
29 | private static final int MIN_SIZE = 10;
30 |
31 | private ItemEntry[] mItemEntries;
32 | private int mAdapterSize;
33 | private boolean mRestoringItem;
34 |
35 | private int sizeForPosition(int position) {
36 | int len = mItemEntries.length;
37 | while (len <= position) {
38 | len *= 2;
39 | }
40 |
41 | // We don't apply any constraints while restoring
42 | // item entries.
43 | if (!mRestoringItem && len > mAdapterSize) {
44 | len = mAdapterSize;
45 | }
46 |
47 | return len;
48 | }
49 |
50 | private void ensureSize(int position) {
51 | if (mItemEntries == null) {
52 | mItemEntries = new ItemEntry[Math.max(position, MIN_SIZE) + 1];
53 | Arrays.fill(mItemEntries, null);
54 | } else if (position >= mItemEntries.length) {
55 | ItemEntry[] oldItemEntries = mItemEntries;
56 | mItemEntries = new ItemEntry[sizeForPosition(position)];
57 | System.arraycopy(oldItemEntries, 0, mItemEntries, 0, oldItemEntries.length);
58 | Arrays.fill(mItemEntries, oldItemEntries.length, mItemEntries.length, null);
59 | }
60 | }
61 |
62 | public ItemEntry getItemEntry(int position) {
63 | if (mItemEntries == null || position >= mItemEntries.length) {
64 | return null;
65 | }
66 |
67 | return mItemEntries[position];
68 | }
69 |
70 | public void putItemEntry(int position, ItemEntry entry) {
71 | ensureSize(position);
72 | mItemEntries[position] = entry;
73 | }
74 |
75 | public void restoreItemEntry(int position, ItemEntry entry) {
76 | mRestoringItem = true;
77 | putItemEntry(position, entry);
78 | mRestoringItem = false;
79 | }
80 |
81 | public int size() {
82 | return (mItemEntries != null ? mItemEntries.length : 0);
83 | }
84 |
85 | public void setAdapterSize(int adapterSize) {
86 | mAdapterSize = adapterSize;
87 | }
88 |
89 | public void invalidateItemLanesAfter(int position) {
90 | if (mItemEntries == null || position >= mItemEntries.length) {
91 | return;
92 | }
93 |
94 | for (int i = position; i < mItemEntries.length; i++) {
95 | final ItemEntry entry = mItemEntries[i];
96 | if (entry != null) {
97 | entry.invalidateLane();
98 | }
99 | }
100 | }
101 |
102 | public void clear() {
103 | if (mItemEntries != null) {
104 | Arrays.fill(mItemEntries, null);
105 | }
106 | }
107 |
108 | void offsetForRemoval(int positionStart, int itemCount) {
109 | if (mItemEntries == null || positionStart >= mItemEntries.length) {
110 | return;
111 | }
112 |
113 | ensureSize(positionStart + itemCount);
114 |
115 | System.arraycopy(mItemEntries, positionStart + itemCount, mItemEntries, positionStart,
116 | mItemEntries.length - positionStart - itemCount);
117 | Arrays.fill(mItemEntries, mItemEntries.length - itemCount, mItemEntries.length, null);
118 | }
119 |
120 | void offsetForAddition(int positionStart, int itemCount) {
121 | if (mItemEntries == null || positionStart >= mItemEntries.length) {
122 | return;
123 | }
124 |
125 | ensureSize(positionStart + itemCount);
126 |
127 | System.arraycopy(mItemEntries, positionStart, mItemEntries, positionStart + itemCount,
128 | mItemEntries.length - positionStart - itemCount);
129 | Arrays.fill(mItemEntries, positionStart, positionStart + itemCount, null);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/Lanes.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview;
18 |
19 | import android.graphics.Rect;
20 | import com.owen.tvrecyclerview.TwoWayLayoutManager.Orientation;
21 | import com.owen.tvrecyclerview.TwoWayLayoutManager.Direction;
22 |
23 | public class Lanes {
24 | public static final int NO_LANE = -1;
25 |
26 | private final BaseLayoutManager mLayout;
27 | private final boolean mIsVertical;
28 | private final Rect[] mLanes;
29 | private final Rect[] mSavedLanes;
30 | private final int mLaneSize;
31 |
32 | private final Rect mTempRect = new Rect();
33 | private final LaneInfo mTempLaneInfo = new LaneInfo();
34 |
35 | private Integer mInnerStart;
36 | private Integer mInnerEnd;
37 |
38 | public static class LaneInfo {
39 | public int startLane;
40 | public int anchorLane;
41 |
42 | public boolean isUndefined() {
43 | return (startLane == NO_LANE || anchorLane == NO_LANE);
44 | }
45 |
46 | public void set(int startLane, int anchorLane) {
47 | this.startLane = startLane;
48 | this.anchorLane = anchorLane;
49 | }
50 |
51 | public void setUndefined() {
52 | startLane = NO_LANE;
53 | anchorLane = NO_LANE;
54 | }
55 | }
56 |
57 | public Lanes(BaseLayoutManager layout, Orientation orientation, Rect[] lanes, int laneSize) {
58 | mLayout = layout;
59 | mIsVertical = (orientation == Orientation.VERTICAL);
60 | mLanes = lanes;
61 | mLaneSize = laneSize;
62 |
63 | mSavedLanes = new Rect[mLanes.length];
64 | for (int i = 0; i < mLanes.length; i++) {
65 | mSavedLanes[i] = new Rect();
66 | }
67 | }
68 |
69 | public Lanes(BaseLayoutManager layout, int laneCount) {
70 | mLayout = layout;
71 | mIsVertical = layout.isVertical();
72 |
73 | mLanes = new Rect[laneCount];
74 | mSavedLanes = new Rect[laneCount];
75 | for (int i = 0; i < laneCount; i++) {
76 | mLanes[i] = new Rect();
77 | mSavedLanes[i] = new Rect();
78 | }
79 |
80 | mLaneSize = calculateLaneSize(layout, laneCount);
81 |
82 | final int paddingLeft = layout.getPaddingLeft();
83 | final int paddingTop = layout.getPaddingTop();
84 |
85 | for (int i = 0; i < laneCount; i++) {
86 | final int laneStart = i * mLaneSize;
87 |
88 | final int l = paddingLeft + (mIsVertical ? laneStart : 0);
89 | final int t = paddingTop + (mIsVertical ? 0 : laneStart);
90 | final int r = (mIsVertical ? l + mLaneSize : l);
91 | final int b = (mIsVertical ? t : t + mLaneSize);
92 |
93 | mLanes[i].set(l, t, r, b);
94 | }
95 | }
96 |
97 | public static int calculateLaneSize(BaseLayoutManager layout, int laneCount) {
98 | if (layout.isVertical()) {
99 | final int paddingLeft = layout.getPaddingLeft();
100 | final int paddingRight = layout.getPaddingRight();
101 | final int width = layout.getWidth() - paddingLeft - paddingRight;
102 | return width / laneCount;
103 | } else {
104 | final int paddingTop = layout.getPaddingTop();
105 | final int paddingBottom = layout.getPaddingBottom();
106 | final int height = layout.getHeight() - paddingTop - paddingBottom;
107 | return height / laneCount;
108 | }
109 | }
110 |
111 | private void invalidateEdges() {
112 | mInnerStart = null;
113 | mInnerEnd = null;
114 | }
115 |
116 | public Orientation getOrientation() {
117 | return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL);
118 | }
119 |
120 | public void save() {
121 | for (int i = 0; i < mLanes.length; i++) {
122 | mSavedLanes[i].set(mLanes[i]);
123 | }
124 | }
125 |
126 | public void restore() {
127 | for (int i = 0; i < mLanes.length; i++) {
128 | mLanes[i].set(mSavedLanes[i]);
129 | }
130 | }
131 |
132 | public int getLaneSize() {
133 | return mLaneSize;
134 | }
135 |
136 | public int getCount() {
137 | return mLanes.length;
138 | }
139 |
140 | private void offsetLane(int lane, int offset) {
141 | mLanes[lane].offset(mIsVertical ? 0 : offset,
142 | mIsVertical ? offset : 0);
143 | }
144 |
145 | public void offset(int offset) {
146 | for (int i = 0; i < mLanes.length; i++) {
147 | offset(i, offset);
148 | }
149 |
150 | invalidateEdges();
151 | }
152 |
153 | public void offset(int lane, int offset) {
154 | offsetLane(lane, offset);
155 | invalidateEdges();
156 | }
157 |
158 | public void getLane(int lane, Rect laneRect) {
159 | laneRect.set(mLanes[lane]);
160 | }
161 |
162 | public int pushChildFrame(Rect outRect, int lane, int margin, Direction direction) {
163 | final int delta;
164 |
165 | final Rect laneRect = mLanes[lane];
166 | if (mIsVertical) {
167 | if (direction == Direction.END) {
168 | delta = outRect.top - laneRect.bottom;
169 | laneRect.bottom = outRect.bottom + margin;
170 | } else {
171 | delta = outRect.bottom - laneRect.top;
172 | laneRect.top = outRect.top - margin;
173 | }
174 | } else {
175 | if (direction == Direction.END) {
176 | delta = outRect.left - laneRect.right;
177 | laneRect.right = outRect.right + margin;
178 | } else {
179 | delta = outRect.right - laneRect.left;
180 | laneRect.left = outRect.left - margin;
181 | }
182 | }
183 |
184 | invalidateEdges();
185 |
186 | return delta;
187 | }
188 |
189 | public void popChildFrame(Rect outRect, int lane, int margin, Direction direction) {
190 | final Rect laneRect = mLanes[lane];
191 | if (mIsVertical) {
192 | if (direction == Direction.END) {
193 | laneRect.top = outRect.bottom - margin;
194 | } else {
195 | laneRect.bottom = outRect.top + margin;
196 | }
197 | } else {
198 | if (direction == Direction.END) {
199 | laneRect.left = outRect.right - margin;
200 | } else {
201 | laneRect.right = outRect.left + margin;
202 | }
203 | }
204 |
205 | invalidateEdges();
206 | }
207 |
208 | public void getChildFrame(Rect outRect, int childWidth, int childHeight, LaneInfo laneInfo,
209 | Direction direction) {
210 | final Rect startRect = mLanes[laneInfo.startLane];
211 |
212 | // The anchor lane only applies when we're get child frame in the direction
213 | // of the forward scroll. We'll need to rethink this once we start working on
214 | // RTL support.
215 | final int anchorLane =
216 | (direction == Direction.END ? laneInfo.anchorLane : laneInfo.startLane);
217 | final Rect anchorRect = mLanes[anchorLane];
218 |
219 | if (mIsVertical) {
220 | outRect.left = startRect.left;
221 | outRect.top =
222 | (direction == Direction.END ? anchorRect.bottom : anchorRect.top - childHeight);
223 | } else {
224 | outRect.top = startRect.top;
225 | outRect.left =
226 | (direction == Direction.END ? anchorRect.right : anchorRect.left - childWidth);
227 | }
228 |
229 | outRect.right = outRect.left + childWidth;
230 | outRect.bottom = outRect.top + childHeight;
231 | }
232 |
233 | private boolean intersects(int start, int count, Rect r) {
234 | for (int l = start; l < start + count; l++) {
235 | if (Rect.intersects(mLanes[l], r)) {
236 | return true;
237 | }
238 | }
239 |
240 | return false;
241 | }
242 |
243 | private int findLaneThatFitsSpan(int anchorLane, int laneSpan, Direction direction) {
244 | final int findStart = Math.max(0, anchorLane - laneSpan + 1);
245 | final int findEnd = Math.min(findStart + laneSpan, mLanes.length - laneSpan + 1);
246 | for (int l = findStart; l < findEnd; l++) {
247 | mTempLaneInfo.set(l, anchorLane);
248 |
249 | getChildFrame(mTempRect, mIsVertical ? laneSpan * mLaneSize : 1,
250 | mIsVertical ? 1 : laneSpan * mLaneSize, mTempLaneInfo, direction);
251 |
252 | if (!intersects(l, laneSpan, mTempRect)) {
253 | return l;
254 | }
255 | }
256 |
257 | return Lanes.NO_LANE;
258 | }
259 |
260 | public void findLane(LaneInfo outInfo, int laneSpan, Direction direction) {
261 | outInfo.setUndefined();
262 |
263 | int targetEdge = (direction == Direction.END ? Integer.MAX_VALUE : Integer.MIN_VALUE);
264 | for (int l = 0; l < mLanes.length; l++) {
265 | final int laneEdge;
266 | if (mIsVertical) {
267 | laneEdge = (direction == Direction.END ? mLanes[l].bottom : mLanes[l].top);
268 | } else {
269 | laneEdge = (direction == Direction.END ? mLanes[l].right : mLanes[l].left);
270 | }
271 |
272 | if ((direction == Direction.END && laneEdge < targetEdge) ||
273 | (direction == Direction.START && laneEdge > targetEdge)) {
274 |
275 | final int targetLane = findLaneThatFitsSpan(l, laneSpan, direction);
276 | if (targetLane != NO_LANE) {
277 | targetEdge = laneEdge;
278 | outInfo.set(targetLane, l);
279 | }
280 | }
281 | }
282 | }
283 |
284 | public void reset(Direction direction) {
285 | for (int i = 0; i < mLanes.length; i++) {
286 | final Rect laneRect = mLanes[i];
287 | if (mIsVertical) {
288 | if (direction == Direction.START) {
289 | laneRect.bottom = laneRect.top;
290 | } else {
291 | laneRect.top = laneRect.bottom;
292 | }
293 | } else {
294 | if (direction == Direction.START) {
295 | laneRect.right = laneRect.left;
296 | } else {
297 | laneRect.left = laneRect.right;
298 | }
299 | }
300 | }
301 |
302 | invalidateEdges();
303 | }
304 |
305 | public void reset(int offset) {
306 | for (int i = 0; i < mLanes.length; i++) {
307 | final Rect laneRect = mLanes[i];
308 |
309 | laneRect.offsetTo(mIsVertical ? laneRect.left : offset,
310 | mIsVertical ? offset : laneRect.top);
311 |
312 | if (mIsVertical) {
313 | laneRect.bottom = laneRect.top;
314 | } else {
315 | laneRect.right = laneRect.left;
316 | }
317 | }
318 |
319 | invalidateEdges();
320 | }
321 |
322 | public int getInnerStart() {
323 | if (mInnerStart != null) {
324 | return mInnerStart;
325 | }
326 |
327 | mInnerStart = Integer.MIN_VALUE;
328 | for (int i = 0; i < mLanes.length; i++) {
329 | final Rect laneRect = mLanes[i];
330 | mInnerStart = Math.max(mInnerStart, mIsVertical ? laneRect.top : laneRect.left);
331 | }
332 |
333 | return mInnerStart;
334 | }
335 |
336 | public int getInnerEnd() {
337 | if (mInnerEnd != null) {
338 | return mInnerEnd;
339 | }
340 |
341 | mInnerEnd = Integer.MAX_VALUE;
342 | for (int i = 0; i < mLanes.length; i++) {
343 | final Rect laneRect = mLanes[i];
344 | mInnerEnd = Math.min(mInnerEnd, mIsVertical ? laneRect.bottom : laneRect.right);
345 | }
346 |
347 | return mInnerEnd;
348 | }
349 | }
350 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/TwoWayLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview;
18 |
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.graphics.PointF;
22 | import android.os.Bundle;
23 | import android.os.Parcel;
24 | import android.os.Parcelable;
25 | import android.support.v7.widget.LinearSmoothScroller;
26 | import android.support.v7.widget.RecyclerView;
27 | import android.support.v7.widget.RecyclerView.Adapter;
28 | import android.support.v7.widget.RecyclerView.LayoutManager;
29 | import android.support.v7.widget.RecyclerView.LayoutParams;
30 | import android.support.v7.widget.RecyclerView.Recycler;
31 | import android.support.v7.widget.RecyclerView.State;
32 | import android.support.v7.widget.RecyclerView.ViewHolder;
33 | import android.util.AttributeSet;
34 | import android.util.Log;
35 | import android.view.View;
36 | import android.view.ViewGroup.MarginLayoutParams;
37 |
38 | import java.util.List;
39 |
40 | public abstract class TwoWayLayoutManager extends LayoutManager {
41 | private static final String LOGTAG = TwoWayLayoutManager.class.getSimpleName();
42 |
43 | public static enum Orientation {
44 | HORIZONTAL,
45 | VERTICAL
46 | }
47 |
48 | public static enum Direction {
49 | START,
50 | END
51 | }
52 |
53 | protected RecyclerView mRecyclerView;
54 |
55 | private boolean mIsVertical = true;
56 |
57 | private SavedState mPendingSavedState = null;
58 |
59 | private int mPendingScrollPosition = RecyclerView.NO_POSITION;
60 | private int mPendingScrollOffset = 0;
61 |
62 | private int mLayoutStart;
63 | private int mLayoutEnd;
64 |
65 | public TwoWayLayoutManager(Context context, AttributeSet attrs) {
66 | this(context, attrs, 0);
67 | }
68 |
69 | public TwoWayLayoutManager(Context context, AttributeSet attrs, int defStyle) {
70 | final TypedArray a =
71 | context.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView, defStyle, 0);
72 |
73 | final int indexCount = a.getIndexCount();
74 | for (int i = 0; i < indexCount; i++) {
75 | final int attr = a.getIndex(i);
76 |
77 | if (attr == R.styleable.TvRecyclerView_android_orientation) {
78 | final int orientation = a.getInt(attr, -1);
79 | if (orientation >= 0) {
80 | setOrientation(Orientation.values()[orientation]);
81 | }
82 | }
83 | }
84 |
85 | a.recycle();
86 | }
87 |
88 | public TwoWayLayoutManager(Orientation orientation) {
89 | mIsVertical = (orientation == Orientation.VERTICAL);
90 | }
91 |
92 | private int getTotalSpace() {
93 | if (mIsVertical) {
94 | return getHeight() - getPaddingBottom() - getPaddingTop();
95 | } else {
96 | return getWidth() - getPaddingRight() - getPaddingLeft();
97 | }
98 | }
99 |
100 | protected int getStartWithPadding() {
101 | return (mIsVertical ? getPaddingTop() : getPaddingLeft());
102 | }
103 |
104 | protected int getEndWithPadding() {
105 | if (mIsVertical) {
106 | return (getHeight() - getPaddingBottom());
107 | } else {
108 | return (getWidth() - getPaddingRight());
109 | }
110 | }
111 |
112 | protected int getChildStart(View child) {
113 | return (mIsVertical ? getDecoratedTop(child) : getDecoratedLeft(child));
114 | }
115 |
116 | protected int getChildEnd(View child) {
117 | return (mIsVertical ? getDecoratedBottom(child) : getDecoratedRight(child));
118 | }
119 |
120 | protected Adapter getAdapter() {
121 | return (mRecyclerView != null ? mRecyclerView.getAdapter() : null);
122 | }
123 |
124 | private void offsetChildren(int offset) {
125 | if (mIsVertical) {
126 | offsetChildrenVertical(offset);
127 | } else {
128 | offsetChildrenHorizontal(offset);
129 | }
130 |
131 | mLayoutStart += offset;
132 | mLayoutEnd += offset;
133 | }
134 |
135 | private void recycleChildrenOutOfBounds(Direction direction, Recycler recycler) {
136 | if (direction == Direction.END) {
137 | recycleChildrenFromStart(direction, recycler);
138 | } else {
139 | recycleChildrenFromEnd(direction, recycler);
140 | }
141 | }
142 |
143 | private void recycleChildrenFromStart(Direction direction, Recycler recycler) {
144 | final int childCount = getChildCount();
145 | final int childrenStart = getStartWithPadding();
146 |
147 | int detachedCount = 0;
148 | for (int i = 0; i < childCount; i++) {
149 | final View child = getChildAt(i);
150 | final int childEnd = getChildEnd(child);
151 |
152 | if (childEnd >= childrenStart) {
153 | break;
154 | }
155 |
156 | detachedCount++;
157 |
158 | detachChild(child, direction);
159 | }
160 |
161 | while (--detachedCount >= 0) {
162 | final View child = getChildAt(0);
163 | removeAndRecycleView(child, recycler);
164 | updateLayoutEdgesFromRemovedChild(child, direction);
165 | }
166 | }
167 |
168 | private void recycleChildrenFromEnd(Direction direction, Recycler recycler) {
169 | final int childrenEnd = getEndWithPadding();
170 | final int childCount = getChildCount();
171 |
172 | int firstDetachedPos = 0;
173 | int detachedCount = 0;
174 | for (int i = childCount - 1; i >= 0; i--) {
175 | final View child = getChildAt(i);
176 | final int childStart = getChildStart(child);
177 |
178 | if (childStart <= childrenEnd) {
179 | break;
180 | }
181 |
182 | firstDetachedPos = i;
183 | detachedCount++;
184 |
185 | detachChild(child, direction);
186 | }
187 |
188 | while (--detachedCount >= 0) {
189 | final View child = getChildAt(firstDetachedPos);
190 | removeAndRecycleViewAt(firstDetachedPos, recycler);
191 | updateLayoutEdgesFromRemovedChild(child, direction);
192 | }
193 | }
194 |
195 | private int scrollBy(int delta, Recycler recycler, State state) {
196 | final int childCount = getChildCount();
197 | if (childCount == 0 || delta == 0) {
198 | return 0;
199 | }
200 |
201 | final int start = getStartWithPadding();
202 | final int end = getEndWithPadding();
203 | final int firstPosition = getFirstVisiblePosition();
204 |
205 | final int totalSpace = getTotalSpace();
206 | if (delta < 0) {
207 | delta = Math.max(-(totalSpace - 1), delta);
208 | } else {
209 | delta = Math.min(totalSpace - 1, delta);
210 | }
211 |
212 | final boolean cannotScrollBackward = (firstPosition == 0 &&
213 | mLayoutStart >= start && delta <= 0);
214 | final boolean cannotScrollForward = (firstPosition + childCount == state.getItemCount() &&
215 | mLayoutEnd <= end && delta >= 0);
216 |
217 | if (cannotScrollForward || cannotScrollBackward) {
218 | return 0;
219 | }
220 |
221 | offsetChildren(-delta);
222 |
223 | final Direction direction = (delta > 0 ? Direction.END : Direction.START);
224 | recycleChildrenOutOfBounds(direction, recycler);
225 |
226 | final int absDelta = Math.abs(delta);
227 | if (canAddMoreViews(Direction.START, start - absDelta) ||
228 | canAddMoreViews(Direction.END, end + absDelta)) {
229 | fillGap(direction, recycler, state);
230 | }
231 |
232 | return delta;
233 | }
234 |
235 | private void fillGap(Direction direction, Recycler recycler, State state) {
236 | final int childCount = getChildCount();
237 | final int extraSpace = getExtraLayoutSpace(state);
238 | final int firstPosition = getFirstVisiblePosition();
239 |
240 | if (direction == Direction.END) {
241 | fillAfter(firstPosition + childCount, recycler, state, extraSpace);
242 | correctTooHigh(childCount, recycler, state);
243 | } else {
244 | fillBefore(firstPosition - 1, recycler, extraSpace);
245 | correctTooLow(childCount, recycler, state);
246 | }
247 | }
248 |
249 | private void fillBefore(int pos, Recycler recycler) {
250 | fillBefore(pos, recycler, 0);
251 | }
252 |
253 | private void fillBefore(int position, Recycler recycler, int extraSpace) {
254 | final int limit = getStartWithPadding() - extraSpace;
255 |
256 | while (canAddMoreViews(Direction.START, limit) && position >= 0) {
257 | makeAndAddView(position, Direction.START, recycler);
258 | position--;
259 | }
260 | }
261 |
262 | private void fillAfter(int pos, Recycler recycler, State state) {
263 | fillAfter(pos, recycler, state, 0);
264 | }
265 |
266 | private void fillAfter(int position, Recycler recycler, State state, int extraSpace) {
267 | final int limit = getEndWithPadding() + extraSpace;
268 |
269 | final int itemCount = state.getItemCount();
270 | while (canAddMoreViews(Direction.END, limit) && position < itemCount) {
271 | makeAndAddView(position, Direction.END, recycler);
272 | position++;
273 | }
274 | }
275 |
276 | private void fillSpecific(int position, Recycler recycler, State state) {
277 | if (state.getItemCount() <= 0) {
278 | return;
279 | }
280 |
281 | makeAndAddView(position, Direction.END, recycler);
282 |
283 | final int extraSpaceBefore;
284 | final int extraSpaceAfter;
285 |
286 | final int extraSpace = getExtraLayoutSpace(state);
287 | if (state.getTargetScrollPosition() < position) {
288 | extraSpaceAfter = 0;
289 | extraSpaceBefore = extraSpace;
290 | } else {
291 | extraSpaceAfter = extraSpace;
292 | extraSpaceBefore = 0;
293 | }
294 |
295 | fillBefore(position - 1, recycler, extraSpaceBefore);
296 |
297 | // This will correct for the top of the first view not
298 | // touching the top of the parent.
299 | adjustViewsStartOrEnd();
300 |
301 | fillAfter(position + 1, recycler, state, extraSpaceAfter);
302 | correctTooHigh(getChildCount(), recycler, state);
303 | }
304 |
305 | private void correctTooHigh(int childCount, Recycler recycler, State state) {
306 | // First see if the last item is visible. If it is not, it is OK for the
307 | // top of the list to be pushed up.
308 | final int lastPosition = getLastVisiblePosition();
309 | if (lastPosition != state.getItemCount() - 1 || childCount == 0) {
310 | return;
311 | }
312 |
313 | // This is bottom of our drawable area.
314 | final int start = getStartWithPadding();
315 | final int end = getEndWithPadding();
316 | final int firstPosition = getFirstVisiblePosition();
317 |
318 | // This is how far the end edge of the last view is from the end of the
319 | // drawable area.
320 | int endOffset = end - mLayoutEnd;
321 |
322 | // Make sure we are 1) Too high, and 2) Either there are more rows above the
323 | // first row or the first row is scrolled off the top of the drawable area
324 | if (endOffset > 0 && (firstPosition > 0 || mLayoutStart < start)) {
325 | if (firstPosition == 0) {
326 | // Don't pull the top too far down.
327 | endOffset = Math.min(endOffset, start - mLayoutStart);
328 | }
329 |
330 | // Move everything down
331 | offsetChildren(endOffset);
332 |
333 | if (firstPosition > 0) {
334 | // Fill the gap that was opened above first position with more
335 | // children, if possible.
336 | fillBefore(firstPosition - 1, recycler);
337 |
338 | // Close up the remaining gap.
339 | adjustViewsStartOrEnd();
340 | }
341 | }
342 | }
343 |
344 | private void correctTooLow(int childCount, Recycler recycler, State state) {
345 | // First see if the first item is visible. If it is not, it is OK for the
346 | // end of the list to be pushed forward.
347 | final int firstPosition = getFirstVisiblePosition();
348 | if (firstPosition != 0 || childCount == 0) {
349 | return;
350 | }
351 |
352 | final int start = getStartWithPadding();
353 | final int end = getEndWithPadding();
354 | final int itemCount = state.getItemCount();
355 | final int lastPosition = getLastVisiblePosition();
356 |
357 | // This is how far the start edge of the first view is from the start of the
358 | // drawable area.
359 | int startOffset = mLayoutStart - start;
360 |
361 | // Make sure we are 1) Too low, and 2) Either there are more columns/rows below the
362 | // last column/row or the last column/row is scrolled off the end of the
363 | // drawable area.
364 | if (startOffset > 0) {
365 | if (lastPosition < itemCount - 1 || mLayoutEnd > end) {
366 | if (lastPosition == itemCount - 1) {
367 | // Don't pull the bottom too far up.
368 | startOffset = Math.min(startOffset, mLayoutEnd - end);
369 | }
370 |
371 | // Move everything up.
372 | offsetChildren(-startOffset);
373 |
374 | if (lastPosition < itemCount - 1) {
375 | // Fill the gap that was opened below the last position with more
376 | // children, if possible.
377 | fillAfter(lastPosition + 1, recycler, state);
378 |
379 | // Close up the remaining gap.
380 | adjustViewsStartOrEnd();
381 | }
382 | } else if (lastPosition == itemCount - 1) {
383 | adjustViewsStartOrEnd();
384 | }
385 | }
386 | }
387 |
388 | private void adjustViewsStartOrEnd() {
389 | if (getChildCount() == 0) {
390 | return;
391 | }
392 |
393 | int delta = mLayoutStart - getStartWithPadding();
394 | if (delta < 0) {
395 | // We only are looking to see if we are too low, not too high
396 | delta = 0;
397 | }
398 |
399 | if (delta != 0) {
400 | offsetChildren(-delta);
401 | }
402 | }
403 |
404 | private static View findNextScrapView(List scrapList, Direction direction,
405 | int position) {
406 | final int scrapCount = scrapList.size();
407 |
408 | ViewHolder closest = null;
409 | int closestDistance = Integer.MAX_VALUE;
410 |
411 | for (int i = 0; i < scrapCount; i++) {
412 | final ViewHolder holder = scrapList.get(i);
413 |
414 | final int distance = holder.getPosition() - position;
415 | if ((distance < 0 && direction == Direction.END) ||
416 | (distance > 0 && direction == Direction.START)) {
417 | continue;
418 | }
419 |
420 | final int absDistance = Math.abs(distance);
421 | if (absDistance < closestDistance) {
422 | closest = holder;
423 | closestDistance = absDistance;
424 |
425 | if (distance == 0) {
426 | break;
427 | }
428 | }
429 | }
430 |
431 | if (closest != null) {
432 | return closest.itemView;
433 | }
434 |
435 | return null;
436 | }
437 |
438 | private void fillFromScrapList(List scrapList, Direction direction) {
439 | final int firstPosition = getFirstVisiblePosition();
440 |
441 | int position;
442 | if (direction == Direction.END) {
443 | position = firstPosition + getChildCount();
444 | } else {
445 | position = firstPosition - 1;
446 | }
447 |
448 | View scrapChild;
449 | while ((scrapChild = findNextScrapView(scrapList, direction, position)) != null) {
450 | setupChild(scrapChild, direction);
451 | position += (direction == Direction.END ? 1 : -1);
452 | }
453 | }
454 |
455 | private void setupChild(View child, Direction direction) {
456 | /*final ItemSelectionSupport itemSelection = ItemSelectionSupport.from(mRecyclerView);
457 | if (itemSelection != null) {
458 | final int position = getPosition(child);
459 | itemSelection.setViewChecked(child, itemSelection.isItemChecked(position));
460 | }*/
461 |
462 | measureChild(child, direction);
463 | layoutChild(child, direction);
464 | }
465 |
466 | private View makeAndAddView(int position, Direction direction, Recycler recycler) {
467 | final View child = recycler.getViewForPosition(position);
468 | final boolean isItemRemoved = ((LayoutParams) child.getLayoutParams()).isItemRemoved();
469 |
470 | if (!isItemRemoved) {
471 | addView(child, (direction == Direction.END ? -1 : 0));
472 | }
473 |
474 | setupChild(child, direction);
475 |
476 | if (!isItemRemoved) {
477 | updateLayoutEdgesFromNewChild(child);
478 | }
479 |
480 | return child;
481 | }
482 |
483 | private void handleUpdate() {
484 | // Refresh state by requesting layout without changing the
485 | // first visible position. This will ensure the layout will
486 | // sync with the adapter changes.
487 | final int firstPosition = getFirstVisiblePosition();
488 | final View firstChild = findViewByPosition(firstPosition);
489 | if (firstChild != null) {
490 | setPendingScrollPositionWithOffset(firstPosition, getChildStart(firstChild));
491 | } else {
492 | setPendingScrollPositionWithOffset(RecyclerView.NO_POSITION, 0);
493 | }
494 | }
495 |
496 | private void updateLayoutEdgesFromNewChild(View newChild) {
497 | final int childStart = getChildStart(newChild);
498 | if (childStart < mLayoutStart) {
499 | mLayoutStart = childStart;
500 | }
501 |
502 | final int childEnd = getChildEnd(newChild);
503 | if (childEnd > mLayoutEnd) {
504 | mLayoutEnd = childEnd;
505 | }
506 | }
507 |
508 | private void updateLayoutEdgesFromRemovedChild(View removedChild, Direction direction) {
509 | final int childCount = getChildCount();
510 | if (childCount == 0) {
511 | resetLayoutEdges();
512 | return;
513 | }
514 |
515 | final int removedChildStart = getChildStart(removedChild);
516 | final int removedChildEnd = getChildEnd(removedChild);
517 |
518 | if (removedChildStart > mLayoutStart && removedChildEnd < mLayoutEnd) {
519 | return;
520 | }
521 |
522 | int index;
523 | final int limit;
524 | if (direction == Direction.END) {
525 | // Scrolling towards the end of the layout, child view being
526 | // removed from the start.
527 | mLayoutStart = Integer.MAX_VALUE;
528 | index = 0;
529 | limit = removedChildEnd;
530 | } else {
531 | // Scrolling towards the start of the layout, child view being
532 | // removed from the end.
533 | mLayoutEnd = Integer.MIN_VALUE;
534 | index = childCount - 1;
535 | limit = removedChildStart;
536 | }
537 |
538 | while (index >= 0 && index <= childCount - 1) {
539 | final View child = getChildAt(index);
540 |
541 | if (direction == Direction.END) {
542 | final int childStart = getChildStart(child);
543 | if (childStart < mLayoutStart) {
544 | mLayoutStart = childStart;
545 | }
546 |
547 | // Checked enough child views to update the minimum
548 | // layout start edge, stop.
549 | if (childStart >= limit) {
550 | break;
551 | }
552 |
553 | index++;
554 | } else {
555 | final int childEnd = getChildEnd(child);
556 | if (childEnd > mLayoutEnd) {
557 | mLayoutEnd = childEnd;
558 | }
559 |
560 | // Checked enough child views to update the minimum
561 | // layout end edge, stop.
562 | if (childEnd <= limit) {
563 | break;
564 | }
565 |
566 | index--;
567 | }
568 | }
569 | }
570 |
571 | private void resetLayoutEdges() {
572 | mLayoutStart = getStartWithPadding();
573 | mLayoutEnd = mLayoutStart;
574 | }
575 |
576 | protected int getExtraLayoutSpace(State state) {
577 | if (state.hasTargetScrollPosition()) {
578 | return getTotalSpace();
579 | } else {
580 | return 0;
581 | }
582 | }
583 |
584 | private Bundle getPendingItemSelectionState() {
585 | if (mPendingSavedState != null) {
586 | return mPendingSavedState.itemSelectionState;
587 | }
588 |
589 | return null;
590 | }
591 |
592 | protected void setPendingScrollPositionWithOffset(int position, int offset) {
593 | mPendingScrollPosition = position;
594 | mPendingScrollOffset = offset;
595 | }
596 |
597 | protected int getPendingScrollPosition() {
598 | if (mPendingSavedState != null) {
599 | return mPendingSavedState.anchorItemPosition;
600 | }
601 |
602 | return mPendingScrollPosition;
603 | }
604 |
605 | protected int getPendingScrollOffset() {
606 | if (mPendingSavedState != null) {
607 | return 0;
608 | }
609 |
610 | return mPendingScrollOffset;
611 | }
612 |
613 | protected int getAnchorItemPosition(State state) {
614 | final int itemCount = state.getItemCount();
615 |
616 | int pendingPosition = getPendingScrollPosition();
617 | if (pendingPosition != RecyclerView.NO_POSITION) {
618 | if (pendingPosition < 0 || pendingPosition >= itemCount) {
619 | pendingPosition = RecyclerView.NO_POSITION;
620 | }
621 | }
622 |
623 | if (pendingPosition != RecyclerView.NO_POSITION) {
624 | return pendingPosition;
625 | } else if (getChildCount() > 0) {
626 | return findFirstValidChildPosition(itemCount);
627 | } else {
628 | return 0;
629 | }
630 | }
631 |
632 | private int findFirstValidChildPosition(int itemCount) {
633 | final int childCount = getChildCount();
634 | for (int i = 0; i < childCount; i++) {
635 | final View view = getChildAt(i);
636 | final int position = getPosition(view);
637 | if (position >= 0 && position < itemCount) {
638 | return position;
639 | }
640 | }
641 |
642 | return 0;
643 | }
644 |
645 | @Override
646 | public int getDecoratedMeasuredWidth(View child) {
647 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
648 | return super.getDecoratedMeasuredWidth(child) + lp.leftMargin + lp.rightMargin;
649 | }
650 |
651 | @Override
652 | public int getDecoratedMeasuredHeight(View child) {
653 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
654 | return super.getDecoratedMeasuredHeight(child) + lp.topMargin + lp.bottomMargin;
655 | }
656 |
657 | @Override
658 | public int getDecoratedLeft(View child) {
659 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
660 | return super.getDecoratedLeft(child) - lp.leftMargin;
661 | }
662 |
663 | @Override
664 | public int getDecoratedTop(View child) {
665 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
666 | return super.getDecoratedTop(child) - lp.topMargin;
667 | }
668 |
669 | @Override
670 | public int getDecoratedRight(View child) {
671 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
672 | return super.getDecoratedRight(child) + lp.rightMargin;
673 | }
674 |
675 | @Override
676 | public int getDecoratedBottom(View child) {
677 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
678 | return super.getDecoratedBottom(child) + lp.bottomMargin;
679 | }
680 |
681 | @Override
682 | public void layoutDecorated(View child, int left, int top, int right, int bottom) {
683 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
684 | super.layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin,
685 | right - lp.rightMargin, bottom - lp.bottomMargin);
686 | }
687 |
688 | @Override
689 | public void onAttachedToWindow(RecyclerView view) {
690 | super.onAttachedToWindow(view);
691 | mRecyclerView = view;
692 |
693 | // add by zhousuqiang 修复第一次获取焦点时leftPadding会自动增加
694 | mLayoutStart = getStartWithPadding();
695 | }
696 |
697 | @Override
698 | public void onDetachedFromWindow(RecyclerView view, Recycler recycler) {
699 | super.onDetachedFromWindow(view, recycler);
700 | mRecyclerView = null;
701 | }
702 |
703 | @Override
704 | public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
705 | super.onAdapterChanged(oldAdapter, newAdapter);
706 |
707 | /*final ItemSelectionSupport itemSelectionSupport = ItemSelectionSupport.from(mRecyclerView);
708 | if (oldAdapter != null && itemSelectionSupport != null) {
709 | itemSelectionSupport.clearChoices();
710 | }*/
711 | }
712 |
713 | @Override
714 | public void onLayoutChildren(Recycler recycler, State state) {
715 | /*final ItemSelectionSupport itemSelection = ItemSelectionSupport.from(mRecyclerView);
716 | if (itemSelection != null) {
717 | final Bundle itemSelectionState = getPendingItemSelectionState();
718 | if (itemSelectionState != null) {
719 | itemSelection.onRestoreInstanceState(itemSelectionState);
720 | }
721 |
722 | if (state.didStructureChange()) {
723 | itemSelection.onAdapterDataChanged();
724 | }
725 | }*/
726 |
727 | final int anchorItemPosition = getAnchorItemPosition(state);
728 | detachAndScrapAttachedViews(recycler);
729 | fillSpecific(anchorItemPosition, recycler, state);
730 |
731 | onLayoutScrapList(recycler, state);
732 |
733 | setPendingScrollPositionWithOffset(RecyclerView.NO_POSITION, 0);
734 | mPendingSavedState = null;
735 | }
736 |
737 | protected void onLayoutScrapList(Recycler recycler, State state) {
738 | final int childCount = getChildCount();
739 | if (childCount == 0 || state.isPreLayout() || !supportsPredictiveItemAnimations()) {
740 | return;
741 | }
742 |
743 | final List scrapList = recycler.getScrapList();
744 | fillFromScrapList(scrapList, Direction.START);
745 | fillFromScrapList(scrapList, Direction.END);
746 | }
747 |
748 | protected void detachChild(View child, Direction direction) {
749 | // Do nothing by default.
750 | }
751 |
752 | @Override
753 | public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
754 | handleUpdate();
755 | }
756 |
757 | @Override
758 | public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
759 | handleUpdate();
760 | }
761 |
762 | @Override
763 | public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
764 | handleUpdate();
765 | }
766 |
767 | @Override
768 | public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
769 | handleUpdate();
770 | }
771 |
772 | @Override
773 | public void onItemsChanged(RecyclerView recyclerView) {
774 | handleUpdate();
775 | }
776 |
777 | @Override
778 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
779 | if (mIsVertical) {
780 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
781 | } else {
782 | return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
783 | }
784 | }
785 |
786 | @Override
787 | public boolean supportsPredictiveItemAnimations() {
788 | return true;
789 | }
790 |
791 | @Override
792 | public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
793 | if (mIsVertical) {
794 | return 0;
795 | }
796 |
797 | return scrollBy(dx, recycler, state);
798 | }
799 |
800 | @Override
801 | public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
802 | if (!mIsVertical) {
803 | return 0;
804 | }
805 |
806 | return scrollBy(dy, recycler, state);
807 | }
808 |
809 | @Override
810 | public boolean canScrollHorizontally() {
811 | return !mIsVertical;
812 | }
813 |
814 | @Override
815 | public boolean canScrollVertically() {
816 | return mIsVertical;
817 | }
818 |
819 | @Override
820 | public void scrollToPosition(int position) {
821 | scrollToPositionWithOffset(position, 0);
822 | }
823 |
824 | public void scrollToPositionWithOffset(int position, int offset) {
825 | setPendingScrollPositionWithOffset(position, offset);
826 | requestLayout();
827 | }
828 |
829 | @Override
830 | public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position) {
831 | final LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
832 | @Override
833 | public PointF computeScrollVectorForPosition(int targetPosition) {
834 | if (getChildCount() == 0) {
835 | return null;
836 | }
837 |
838 | final int direction = targetPosition < getFirstVisiblePosition() ? -1 : 1;
839 | if (mIsVertical) {
840 | return new PointF(0, direction);
841 | } else {
842 | return new PointF(direction, 0);
843 | }
844 | }
845 |
846 | @Override
847 | protected int getVerticalSnapPreference() {
848 | return LinearSmoothScroller.SNAP_TO_END;
849 | }
850 |
851 | @Override
852 | protected int getHorizontalSnapPreference() {
853 | return LinearSmoothScroller.SNAP_TO_END;
854 | }
855 | };
856 |
857 | scroller.setTargetPosition(position);
858 | startSmoothScroll(scroller);
859 | }
860 |
861 | @Override
862 | public int computeHorizontalScrollOffset(State state) {
863 | if (getChildCount() == 0) {
864 | return 0;
865 | }
866 |
867 | return getFirstVisiblePosition();
868 | }
869 |
870 | @Override
871 | public int computeVerticalScrollOffset(State state) {
872 | if (getChildCount() == 0) {
873 | return 0;
874 | }
875 |
876 | return getFirstVisiblePosition();
877 | }
878 |
879 | @Override
880 | public int computeHorizontalScrollExtent(State state) {
881 | return getChildCount();
882 | }
883 |
884 | @Override
885 | public int computeVerticalScrollExtent(State state) {
886 | return getChildCount();
887 | }
888 |
889 | @Override
890 | public int computeHorizontalScrollRange(State state) {
891 | return state.getItemCount();
892 | }
893 |
894 | @Override
895 | public int computeVerticalScrollRange(State state) {
896 | return state.getItemCount();
897 | }
898 |
899 | @Override
900 | public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
901 | super.onMeasure(recycler, state, widthSpec, heightSpec);
902 | }
903 |
904 | @Override
905 | public Parcelable onSaveInstanceState() {
906 | final SavedState state = new SavedState(SavedState.EMPTY_STATE);
907 |
908 | int anchorItemPosition = getPendingScrollPosition();
909 | if (anchorItemPosition == RecyclerView.NO_POSITION) {
910 | anchorItemPosition = getFirstVisiblePosition();
911 | }
912 | state.anchorItemPosition = anchorItemPosition;
913 | state.itemSelectionState = Bundle.EMPTY;
914 |
915 | /*final ItemSelectionSupport itemSelection = ItemSelectionSupport.from(mRecyclerView);
916 | if (itemSelection != null) {
917 | state.itemSelectionState = itemSelection.onSaveInstanceState();
918 | } else {
919 | state.itemSelectionState = Bundle.EMPTY;
920 | }*/
921 |
922 | return state;
923 | }
924 |
925 | @Override
926 | public void onRestoreInstanceState(Parcelable state) {
927 | mPendingSavedState = (SavedState) state;
928 | requestLayout();
929 | }
930 |
931 | public Orientation getOrientation() {
932 | return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL);
933 | }
934 |
935 | public void setOrientation(Orientation orientation) {
936 | final boolean isVertical = (orientation == Orientation.VERTICAL);
937 | if (this.mIsVertical == isVertical) {
938 | return;
939 | }
940 |
941 | this.mIsVertical = isVertical;
942 | requestLayout();
943 | }
944 |
945 | public int getFirstVisiblePosition() {
946 | if (getChildCount() == 0) {
947 | return 0;
948 | }
949 |
950 | return getPosition(getChildAt(0));
951 | }
952 |
953 | public int getLastVisiblePosition() {
954 | final int childCount = getChildCount();
955 | if (childCount == 0) {
956 | return 0;
957 | }
958 |
959 | return getPosition(getChildAt(childCount - 1));
960 | }
961 |
962 | /**
963 | * add by zhousuqiang
964 | */
965 | // @Override
966 | // public boolean onRequestChildFocus(RecyclerView parent, State state, final View child, View focused) {
967 | // Log.e(LOGTAG ,"onRequestChildFocus...");
968 | // if(null != mRecyclerView) {
969 | // if (mIsVertical) {
970 | // mRecyclerView.scrollBy(1, 0);
971 | // }
972 | // else {
973 | // mRecyclerView.scrollBy(0, 1);
974 | // }
975 | // }
976 | // if(null != child) {
977 | //// smoothToCenter(child);
978 | // child.setOnClickListener(new View.OnClickListener() {
979 | // @Override
980 | // public void onClick(View v) {
981 | // Toast.makeText(v.getContext(), getPosition(child) + "", Toast.LENGTH_SHORT).show();
982 | // }
983 | // });
984 | // }
985 | // return super.onRequestChildFocus(parent, state, child, focused);
986 | // }
987 |
988 | /**
989 | * add by zhousuqiang
990 | */
991 | // private int byValue;
992 | // private long time;
993 | // private float bl = 0.2f;
994 | // @Override
995 | // public View onFocusSearchFailed(View focused, int direction, Recycler recycler, State state) {
996 | // Log.d(LOGTAG, "onFocusSearchFailed...");
997 | //// Log.d(LOGTAG, "前 time = " + time);
998 | //// long difference = System.currentTimeMillis() - time;
999 | //// time = System.currentTimeMillis();
1000 | //// Log.d(LOGTAG, "后 difference = " + difference);
1001 | //// if (difference < 200) {
1002 | //// bl = 1f; // 防止长按遥控器焦点丢失
1003 | //// } else {
1004 | //// bl = 0.2f;
1005 | //// }
1006 | //
1007 | // final boolean cannotScrollBackward = getFirstVisiblePosition() == 0;
1008 | // final boolean cannotScrollForward = getFirstVisiblePosition() + getChildCount() == state.getItemCount();
1009 | // if(cannotScrollBackward && (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT)) {
1010 | // return null;
1011 | // }
1012 | // else if (cannotScrollForward && (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)) {
1013 | // return null;
1014 | // }
1015 | //
1016 | // if(null == focused) return null;
1017 | // if(mIsVertical) {
1018 | // byValue = (int) (focused.getHeight() * bl);
1019 | // } else {
1020 | // byValue = (int) (focused.getWidth() * bl);
1021 | // }
1022 | //
1023 | // switch (direction) {
1024 | // case View.FOCUS_DOWN:
1025 | // scrollVerticallyBy(byValue, recycler, state);
1026 | // break;
1027 | // case View.FOCUS_UP:
1028 | // scrollVerticallyBy(-byValue, recycler, state);
1029 | // break;
1030 | //
1031 | // case View.FOCUS_LEFT:
1032 | // scrollHorizontallyBy(-byValue, recycler, state);
1033 | // break;
1034 | //
1035 | // case View.FOCUS_RIGHT:
1036 | //// smoothScrollToPosition(mRecyclerView, state, getPosition(focused) + 3);
1037 | // scrollToPositionWithOffset(getPosition(focused) + 3, 100);
1038 | //// scrollHorizontallyBy(byValue, recycler, state);
1039 | // break;
1040 | // }
1041 | // return null;
1042 | // }
1043 |
1044 | /**
1045 | * add by zhousuqiang
1046 | * 将指定item平滑移动到整个view的中间位置
1047 | * @param position
1048 | */
1049 | int dx;
1050 | public void smoothToCenter(View targetChild){
1051 | dx = 0;
1052 | int count = getItemCount();
1053 | int childWidth = targetChild.getWidth();
1054 | int parentWidth = getWidth();//获取父视图的宽度
1055 | int childLeftPx = targetChild.getLeft();//子view相对于父view的左边距
1056 | int childRightPx = parentWidth - (childLeftPx + childWidth);//子view相对于父view的右边距
1057 | childLeftPx -= getPaddingLeft();
1058 | childRightPx -= getPaddingRight();
1059 | int childWidthHalf = childWidth / 2;
1060 | Log.i(LOGTAG, "target-->left:" + childLeftPx + " right:" + childRightPx + " childWidthHalf:"+childWidthHalf);
1061 |
1062 | if(childLeftPx < childWidth) {
1063 | mRecyclerView.smoothScrollBy(-(childWidth /2 *3), 0);
1064 | Log.i(LOGTAG,"向左移动...");
1065 | } else if(childRightPx < childWidth) {
1066 | mRecyclerView.smoothScrollBy((childWidth /2 *3), 0);
1067 | Log.i(LOGTAG,"向右移动...");
1068 | }
1069 |
1070 | // int centerLeft = parentWidth/2-childWidth/2;//计算子view居中后相对于父view的左边距
1071 | // int centerRight = parentWidth/2+childWidth/2;//计算子view居中后相对于父view的右边距
1072 | // Log.i(LOGTAG,"parent width:"+parentWidth+" item width:"+childWidth+" centerleft:"+centerLeft+" centerRight:"+centerRight);
1073 |
1074 | // if(childLeftPx > centerLeft){
1075 | // //子view左边距比居中view大(说明子view靠父view的右边,此时需要把子view向左平移
1076 | // //平移的起始位置就是子view的左边距,平移的距离就是两者之差
1077 | // dx = centerLeft - childLeftPx;
1078 | // }else if(childRightPx < centerRight){
1079 | // dx = centerRight - childRightPx;
1080 | //
1081 | // }
1082 | //
1083 | // Log.e(LOGTAG, "DX : " + dx);
1084 | // if(dx > 0) {
1085 | // mRecyclerView.smoothScrollBy(dx, 0);
1086 | // }
1087 |
1088 |
1089 | // int k = checkView.getMeasuredWidth();
1090 | // int l = checkView.getLeft() - mNavScroll.getLeft();
1091 | // int i2 = l + k / 2 - mNavScroll.getMeasuredWidth() / 2;
1092 | // i2 = Math.max(i2, 0);
1093 | // mNavScroll.smoothScrollTo(i2, 0);
1094 | }
1095 |
1096 |
1097 | protected abstract void measureChild(View child, Direction direction);
1098 | protected abstract void layoutChild(View child, Direction direction);
1099 | protected abstract boolean canAddMoreViews(Direction direction, int limit);
1100 |
1101 | protected static class SavedState implements Parcelable {
1102 | protected static final SavedState EMPTY_STATE = new SavedState();
1103 |
1104 | private final Parcelable superState;
1105 | private int anchorItemPosition;
1106 | private Bundle itemSelectionState;
1107 |
1108 | private SavedState() {
1109 | superState = null;
1110 | }
1111 |
1112 | protected SavedState(Parcelable superState) {
1113 | if (superState == null) {
1114 | throw new IllegalArgumentException("superState must not be null");
1115 | }
1116 |
1117 | this.superState = (superState != EMPTY_STATE ? superState : null);
1118 | }
1119 |
1120 | protected SavedState(Parcel in) {
1121 | this.superState = EMPTY_STATE;
1122 | anchorItemPosition = in.readInt();
1123 | itemSelectionState = in.readParcelable(getClass().getClassLoader());
1124 | }
1125 |
1126 | public Parcelable getSuperState() {
1127 | return superState;
1128 | }
1129 |
1130 | @Override
1131 | public int describeContents() {
1132 | return 0;
1133 | }
1134 |
1135 | @Override
1136 | public void writeToParcel(Parcel out, int flags) {
1137 | out.writeInt(anchorItemPosition);
1138 | out.writeParcelable(itemSelectionState, flags);
1139 | }
1140 |
1141 | public static final Creator CREATOR
1142 | = new Creator() {
1143 | @Override
1144 | public SavedState createFromParcel(Parcel in) {
1145 | return new SavedState(in);
1146 | }
1147 |
1148 | @Override
1149 | public SavedState[] newArray(int size) {
1150 | return new SavedState[size];
1151 | }
1152 | };
1153 | }
1154 | }
1155 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/DividerItemDecoration.java:
--------------------------------------------------------------------------------
1 | package com.owen.tvrecyclerview.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Rect;
7 | import android.graphics.drawable.Drawable;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.support.v7.widget.RecyclerView.ItemDecoration;
10 | import android.util.AttributeSet;
11 | import android.view.View;
12 | import android.view.ViewGroup.MarginLayoutParams;
13 |
14 | import com.owen.tvrecyclerview.BaseLayoutManager;
15 | import com.owen.tvrecyclerview.R;
16 |
17 | /**
18 | * {@link android.support.v7.widget.RecyclerView.ItemDecoration} that draws
19 | * vertical and horizontal dividers between the items of the target
20 | * {@link android.support.v7.widget.RecyclerView}.
21 | */
22 | public class DividerItemDecoration extends ItemDecoration {
23 | private final ItemSpacingOffsets mItemSpacing;
24 |
25 | private final Drawable mVerticalDivider;
26 | private final Drawable mHorizontalDivider;
27 |
28 | public DividerItemDecoration(Context context, AttributeSet attrs) {
29 | this(context, attrs, 0);
30 | }
31 |
32 | public DividerItemDecoration(Context context, AttributeSet attrs, int defStyle) {
33 | final TypedArray a =
34 | context.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView_DividerItemDecoration, defStyle, 0);
35 |
36 | final Drawable divider = a.getDrawable(R.styleable.TvRecyclerView_DividerItemDecoration_android_divider);
37 | if (divider != null) {
38 | mVerticalDivider = mHorizontalDivider = divider;
39 | } else {
40 | mVerticalDivider = a.getDrawable(R.styleable.TvRecyclerView_DividerItemDecoration_verticalDivider);
41 | mHorizontalDivider = a.getDrawable(R.styleable.TvRecyclerView_DividerItemDecoration_horizontalDivider);
42 | }
43 |
44 | a.recycle();
45 |
46 | mItemSpacing = createSpacing(mVerticalDivider, mHorizontalDivider);
47 | }
48 |
49 | public DividerItemDecoration(Drawable divider) {
50 | this(divider, divider);
51 | }
52 |
53 | public DividerItemDecoration(Drawable verticalDivider, Drawable horizontalDivider) {
54 | mVerticalDivider = verticalDivider;
55 | mHorizontalDivider = horizontalDivider;
56 | mItemSpacing = createSpacing(mVerticalDivider, mHorizontalDivider);
57 | }
58 |
59 | private static ItemSpacingOffsets createSpacing(Drawable verticalDivider,
60 | Drawable horizontalDivider) {
61 | final int verticalSpacing;
62 | if (horizontalDivider != null) {
63 | verticalSpacing = horizontalDivider.getIntrinsicHeight();
64 | } else {
65 | verticalSpacing = 0;
66 | }
67 |
68 | final int horizontalSpacing;
69 | if (verticalDivider != null) {
70 | horizontalSpacing = verticalDivider.getIntrinsicWidth();
71 | } else {
72 | horizontalSpacing = 0;
73 | }
74 |
75 | final ItemSpacingOffsets spacing = new ItemSpacingOffsets(verticalSpacing, horizontalSpacing);
76 | spacing.setAddSpacingAtEnd(true);
77 |
78 | return spacing;
79 | }
80 |
81 | @Override
82 | public void onDrawOver(Canvas c, RecyclerView parent) {
83 | final BaseLayoutManager lm = (BaseLayoutManager) parent.getLayoutManager();
84 |
85 | final int rightWithPadding = parent.getWidth() - parent.getPaddingRight();
86 | final int bottomWithPadding = parent.getHeight() - parent.getPaddingBottom();
87 |
88 | final int childCount = parent.getChildCount();
89 | for (int i = 0; i < childCount; i++) {
90 | final View child = parent.getChildAt(i);
91 |
92 | final int childLeft = lm.getDecoratedLeft(child);
93 | final int childTop = lm.getDecoratedTop(child);
94 | final int childRight = lm.getDecoratedRight(child);
95 | final int childBottom = lm.getDecoratedBottom(child);
96 |
97 | final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
98 |
99 | final int bottomOffset = childBottom - child.getBottom() - lp.bottomMargin;
100 | if (bottomOffset > 0 && childBottom < bottomWithPadding) {
101 | final int left = childLeft;
102 | final int top = childBottom - bottomOffset;
103 | final int right = childRight;
104 | final int bottom = top + mHorizontalDivider.getIntrinsicHeight();
105 |
106 | mHorizontalDivider.setBounds(left, top, right, bottom);
107 | mHorizontalDivider.draw(c);
108 | }
109 |
110 | final int rightOffset = childRight - child.getRight() - lp.rightMargin;
111 | if (rightOffset > 0 && childRight < rightWithPadding) {
112 | final int left = childRight - rightOffset;
113 | final int top = childTop;
114 | final int right = left + mVerticalDivider.getIntrinsicWidth();
115 | final int bottom = childBottom;
116 |
117 | mVerticalDivider.setBounds(left, top, right, bottom);
118 | mVerticalDivider.draw(c);
119 | }
120 | }
121 | }
122 |
123 | @Override
124 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
125 | mItemSpacing.getItemOffsets(outRect, itemPosition, parent);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/GridLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.widget;
18 |
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.support.v7.widget.RecyclerView.Recycler;
22 | import android.support.v7.widget.RecyclerView.State;
23 | import android.util.AttributeSet;
24 | import android.view.View;
25 |
26 | import com.owen.tvrecyclerview.BaseLayoutManager;
27 | import com.owen.tvrecyclerview.Lanes;
28 | import com.owen.tvrecyclerview.Lanes.LaneInfo;
29 | import com.owen.tvrecyclerview.R;
30 |
31 |
32 | public class GridLayoutManager extends BaseLayoutManager {
33 | private static final String LOGTAG = "GridLayoutManager";
34 |
35 | private static final int DEFAULT_NUM_COLS = 2;
36 | private static final int DEFAULT_NUM_ROWS = 2;
37 |
38 | private int mNumColumns;
39 | private int mNumRows;
40 |
41 | public GridLayoutManager(Context context, AttributeSet attrs) {
42 | this(context, attrs, 0);
43 | }
44 |
45 | public GridLayoutManager(Context context, AttributeSet attrs, int defStyle) {
46 | this(context, attrs, defStyle, DEFAULT_NUM_COLS, DEFAULT_NUM_ROWS);
47 | }
48 |
49 | protected GridLayoutManager(Context context, AttributeSet attrs, int defStyle,
50 | int defaultNumColumns, int defaultNumRows) {
51 | super(context, attrs, defStyle);
52 |
53 | final TypedArray a =
54 | context.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView, defStyle, 0);
55 |
56 | mNumColumns =
57 | Math.max(1, a.getInt(R.styleable.TvRecyclerView_numColumns, defaultNumColumns));
58 | mNumRows =
59 | Math.max(1, a.getInt(R.styleable.TvRecyclerView_numRows, defaultNumRows));
60 |
61 | a.recycle();
62 | }
63 |
64 | public GridLayoutManager(Orientation orientation, int numColumns, int numRows) {
65 | super(orientation);
66 | mNumColumns = numColumns;
67 | mNumRows = numRows;
68 |
69 | if (mNumColumns < 1) {
70 | throw new IllegalArgumentException("GridLayoutManager must have at least 1 column");
71 | }
72 |
73 | if (mNumRows < 1) {
74 | throw new IllegalArgumentException("GridLayoutManager must have at least 1 row");
75 | }
76 | }
77 |
78 | @Override
79 | public int getLaneCount() {
80 | return (isVertical() ? mNumColumns : mNumRows);
81 | }
82 |
83 | @Override
84 | public void getLaneForPosition(LaneInfo outInfo, int position, Direction direction) {
85 | final int lane = (position % getLaneCount());
86 | outInfo.set(lane, lane);
87 | }
88 |
89 | @Override
90 | protected void moveLayoutToPosition(int position, int offset, Recycler recycler, State state) {
91 | final Lanes lanes = getLanes();
92 | lanes.reset(offset);
93 |
94 | getLaneForPosition(mTempLaneInfo, position, Direction.END);
95 | final int lane = mTempLaneInfo.startLane;
96 | if (lane == 0) {
97 | return;
98 | }
99 |
100 | final View child = recycler.getViewForPosition(position);
101 | measureChild(child, Direction.END);
102 |
103 | final int dimension =
104 | (isVertical() ? getDecoratedMeasuredHeight(child) : getDecoratedMeasuredWidth(child));
105 |
106 | for (int i = lane - 1; i >= 0; i--) {
107 | lanes.offset(i, dimension);
108 | }
109 | }
110 |
111 | public int getNumColumns() {
112 | return mNumColumns;
113 | }
114 |
115 | public void setNumColumns(int numColumns) {
116 | if (mNumColumns == numColumns) {
117 | return;
118 | }
119 |
120 | mNumColumns = numColumns;
121 | if (isVertical()) {
122 | requestLayout();
123 | }
124 | }
125 |
126 | public int getNumRows() {
127 | return mNumRows;
128 | }
129 |
130 | public void setNumRows(int numRows) {
131 | if (mNumRows == numRows) {
132 | return;
133 | }
134 |
135 | mNumRows = numRows;
136 | if (!isVertical()) {
137 | requestLayout();
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/ItemSpacingOffsets.java:
--------------------------------------------------------------------------------
1 | package com.owen.tvrecyclerview.widget;
2 |
3 | import android.graphics.Rect;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.util.Log;
6 |
7 | import com.owen.tvrecyclerview.BaseLayoutManager;
8 | import com.owen.tvrecyclerview.Lanes;
9 | import com.owen.tvrecyclerview.Lanes.LaneInfo;
10 | import com.owen.tvrecyclerview.TwoWayLayoutManager.Direction;
11 |
12 |
13 | /**
14 | * Core logic for applying item vertical and horizontal spacings via item
15 | * offsets. Account for the item lane positions to only apply spacings within
16 | * the layout.
17 | */
18 | public class ItemSpacingOffsets {
19 | private final int mVerticalSpacing;
20 | private final int mHorizontalSpacing;
21 |
22 | private boolean mAddSpacingAtEnd;
23 |
24 | private final LaneInfo mTempLaneInfo = new LaneInfo();
25 |
26 | public ItemSpacingOffsets(int verticalSpacing, int horizontalSpacing) {
27 | if (verticalSpacing < 0 || horizontalSpacing < 0) {
28 | throw new IllegalArgumentException("Spacings should be equal or greater than 0");
29 | }
30 |
31 | mVerticalSpacing = verticalSpacing;
32 | mHorizontalSpacing = horizontalSpacing;
33 | }
34 |
35 | // add by zhousuqiang
36 | public int getSpacingSize(BaseLayoutManager lm) {
37 | if(null != lm) {
38 | if(lm.isVertical()) {
39 | return mHorizontalSpacing;
40 | } else {
41 | return mVerticalSpacing;
42 | }
43 | }
44 | return 0;
45 | }
46 |
47 | // add by zhousuqiang
48 | public int getVerticalSpacing() {
49 | return mVerticalSpacing;
50 | }
51 |
52 | // add by zhousuqiang
53 | public int getHorizontalSpacing() {
54 | return mHorizontalSpacing;
55 | }
56 |
57 | /**
58 | * Checks whether the given position is placed just after the item in the
59 | * first lane of the layout taking items spans into account.
60 | */
61 | public boolean isSecondLane(BaseLayoutManager lm, int itemPosition, int lane) {
62 | if (lane == 0 || itemPosition == 0) {
63 | return false;
64 | }
65 |
66 | int previousLane = Lanes.NO_LANE;
67 | int previousPosition = itemPosition - 1;
68 | while (previousPosition >= 0) {
69 | lm.getLaneForPosition(mTempLaneInfo, previousPosition, Direction.END);
70 | previousLane = mTempLaneInfo.startLane;
71 | if (previousLane != lane) {
72 | break;
73 | }
74 |
75 | previousPosition--;
76 | }
77 |
78 | final int previousLaneSpan = lm.getLaneSpanForPosition(previousPosition);
79 | if (previousLane == 0) {
80 | return (lane == previousLane + previousLaneSpan);
81 | }
82 |
83 | return false;
84 | }
85 |
86 | /**
87 | * Checks whether the given position is placed at the start of a layout lane.
88 | */
89 | public static boolean isFirstChildInLane(BaseLayoutManager lm, int itemPosition) {
90 | final int laneCount = lm.getLanes().getCount();
91 | if (itemPosition >= laneCount) {
92 | return false;
93 | }
94 |
95 | int count = 0;
96 | for (int i = 0; i < itemPosition; i++) {
97 | count += lm.getLaneSpanForPosition(i);
98 | if (count >= laneCount) {
99 | return false;
100 | }
101 | }
102 |
103 | return true;
104 | }
105 |
106 | /**
107 | * Checks whether the given position is placed at the end of a layout lane.
108 | */
109 | public static boolean isLastChildInLane(BaseLayoutManager lm, int itemPosition, int itemCount) {
110 | final int laneCount = lm.getLanes().getCount();
111 | Log.d("ItemSpacingOffsets", "isLastChildInLane...itemPosition=" + itemPosition + " , itemCount=" + itemCount + " , laneCount=" + laneCount);
112 | if (itemPosition < itemCount - laneCount) {
113 | return false;
114 | }
115 |
116 | // TODO: Figure out a robust way to compute this for layouts
117 | // that are dynamically placed and might span multiple lanes.
118 | if (lm instanceof SpannableGridLayoutManager || lm instanceof StaggeredGridLayoutManager) {
119 |
120 | return false;
121 | }
122 |
123 | return true;
124 | }
125 |
126 | public void setAddSpacingAtEnd(boolean spacingAtEnd) {
127 | mAddSpacingAtEnd = spacingAtEnd;
128 | }
129 |
130 | /**
131 | * Computes the offsets based on the vertical and horizontal spacing values.
132 | * The spacing computation has to ensure that the lane sizes are the same after
133 | * applying the offsets. This means we have to shift the spacing unevenly across
134 | * items depending on their position in the layout.
135 | */
136 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
137 | final BaseLayoutManager lm = (BaseLayoutManager) parent.getLayoutManager();
138 |
139 | lm.getLaneForPosition(mTempLaneInfo, itemPosition, Direction.END);
140 | final int lane = mTempLaneInfo.startLane;
141 | final int laneSpan = lm.getLaneSpanForPosition(itemPosition);
142 | final int laneCount = lm.getLanes().getCount();
143 | final int itemCount = parent.getAdapter().getItemCount();
144 |
145 | final boolean isVertical = lm.isVertical();
146 |
147 | final boolean firstLane = (lane == 0);
148 | final boolean secondLane = isSecondLane(lm, itemPosition, lane);
149 |
150 | final boolean lastLane = (lane + laneSpan == laneCount);
151 | final boolean beforeLastLane = (lane + laneSpan == laneCount - 1);
152 |
153 | final int laneSpacing = (isVertical ? mHorizontalSpacing : mVerticalSpacing);
154 |
155 | final int laneOffsetStart;
156 | final int laneOffsetEnd;
157 |
158 | if (firstLane) {
159 | laneOffsetStart = 0;
160 | }
161 | else if (lastLane && !secondLane) {
162 | laneOffsetStart = (int) (laneSpacing * 0.65);
163 | }
164 | else if (secondLane && !lastLane) {
165 | laneOffsetStart = (int) (laneSpacing * 0.35);
166 | }
167 | else {
168 | laneOffsetStart = (int) (laneSpacing * 0.5);
169 | }
170 |
171 | if (lastLane) {
172 | laneOffsetEnd = 0;
173 | }
174 | else if (firstLane && !beforeLastLane) {
175 | laneOffsetEnd = (int) (laneSpacing * 0.65);
176 | }
177 | else if (beforeLastLane && !firstLane) {
178 | laneOffsetEnd = (int) (laneSpacing * 0.35);
179 | }
180 | else {
181 | laneOffsetEnd = (int) (laneSpacing * 0.5);
182 | }
183 |
184 | final boolean isFirstInLane = isFirstChildInLane(lm, itemPosition);
185 | final boolean isLastInLane = !mAddSpacingAtEnd &&
186 | isLastChildInLane(lm, itemPosition, itemCount);
187 |
188 | if (isVertical) {
189 | outRect.left = laneOffsetStart;
190 | outRect.top = (isFirstInLane ? 0 : mVerticalSpacing / 2);
191 | outRect.right = laneOffsetEnd;
192 | outRect.bottom = (isLastInLane ? 0 : mVerticalSpacing / 2);
193 | } else {
194 | outRect.left = (isFirstInLane ? 0 : mHorizontalSpacing / 2);
195 | outRect.top = laneOffsetStart;
196 | outRect.right = (isLastInLane ? 0 : mHorizontalSpacing / 2);
197 | outRect.bottom = laneOffsetEnd;
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/ListLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.widget;
18 |
19 | import android.content.Context;
20 | import android.support.v7.widget.RecyclerView.Recycler;
21 | import android.support.v7.widget.RecyclerView.State;
22 | import android.util.AttributeSet;
23 |
24 | import com.owen.tvrecyclerview.BaseLayoutManager;
25 | import com.owen.tvrecyclerview.Lanes.LaneInfo;
26 |
27 |
28 | public class ListLayoutManager extends BaseLayoutManager {
29 | private static final String LOGTAG = "ListLayoutManager";
30 |
31 | public ListLayoutManager(Context context, AttributeSet attrs) {
32 | this(context, attrs, 0);
33 | }
34 |
35 | public ListLayoutManager(Context context, AttributeSet attrs, int defStyle) {
36 | super(context, attrs, defStyle);
37 | }
38 |
39 | public ListLayoutManager(Context context, Orientation orientation) {
40 | super(orientation);
41 | }
42 |
43 | @Override
44 | public int getLaneCount() {
45 | return 1;
46 | }
47 |
48 | @Override
49 | public void getLaneForPosition(LaneInfo outInfo, int position, Direction direction) {
50 | outInfo.set(0, 0);
51 | }
52 |
53 | @Override
54 | protected void moveLayoutToPosition(int position, int offset, Recycler recycler, State state) {
55 | getLanes().reset(offset);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/SpacingItemDecoration.java:
--------------------------------------------------------------------------------
1 | package com.owen.tvrecyclerview.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Rect;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.support.v7.widget.RecyclerView.ItemDecoration;
8 | import android.util.AttributeSet;
9 | import android.util.Log;
10 |
11 | import com.owen.tvrecyclerview.R;
12 |
13 | /**
14 | * {@link android.support.v7.widget.RecyclerView.ItemDecoration} that applies a
15 | * vertical and horizontal spacing between items of the target
16 | * {@link android.support.v7.widget.RecyclerView}.
17 | */
18 | public class SpacingItemDecoration extends ItemDecoration {
19 | protected int mVerticalSpacing;
20 | protected int mHorizontalSpacing;
21 | protected final ItemSpacingOffsets mItemSpacing;
22 |
23 | public SpacingItemDecoration(Context context, AttributeSet attrs) {
24 | this(context, attrs, 0);
25 | }
26 |
27 | public SpacingItemDecoration(Context context, AttributeSet attrs, int defStyle) {
28 | final TypedArray a =
29 | context.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView_SpacingItemDecoration, defStyle, 0);
30 |
31 | mVerticalSpacing =
32 | Math.max(0, a.getInt(R.styleable.TvRecyclerView_SpacingItemDecoration_android_verticalSpacing, 0));
33 | mHorizontalSpacing =
34 | Math.max(0, a.getInt(R.styleable.TvRecyclerView_SpacingItemDecoration_android_horizontalSpacing, 0));
35 |
36 | a.recycle();
37 |
38 | mItemSpacing = new ItemSpacingOffsets(mHorizontalSpacing, mHorizontalSpacing);
39 | }
40 |
41 | public SpacingItemDecoration(int verticalSpacing, int horizontalSpacing) {
42 | mItemSpacing = new ItemSpacingOffsets(verticalSpacing, horizontalSpacing);
43 | }
44 |
45 | @Override
46 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
47 | mItemSpacing.getItemOffsets(outRect, itemPosition, parent);
48 | Log.e("SpacingItemDecoration", "itemPosition=" + itemPosition + " , outRect.right"+ outRect.right
49 | + " , outRect.left="+outRect.left
50 | + " , outRect.top="+outRect.top
51 | + " , outRect.bottom="+outRect.bottom
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/SpannableGridLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.widget;
18 |
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.os.Parcel;
22 | import android.os.Parcelable;
23 | import android.support.v7.widget.RecyclerView;
24 | import android.support.v7.widget.RecyclerView.Recycler;
25 | import android.support.v7.widget.RecyclerView.State;
26 | import android.util.AttributeSet;
27 | import android.view.View;
28 | import android.view.ViewGroup;
29 | import android.view.ViewGroup.MarginLayoutParams;
30 |
31 | import com.owen.tvrecyclerview.BaseLayoutManager;
32 | import com.owen.tvrecyclerview.Lanes;
33 | import com.owen.tvrecyclerview.Lanes.LaneInfo;
34 | import com.owen.tvrecyclerview.R;
35 |
36 | public class SpannableGridLayoutManager extends GridLayoutManager {
37 | public static final String LOGTAG = "SpannableGridLM";
38 |
39 | private static final int DEFAULT_NUM_COLS = 3;
40 | private static final int DEFAULT_NUM_ROWS = 3;
41 |
42 | public static class SpannableItemEntry extends BaseLayoutManager.ItemEntry {
43 | private final int colSpan;
44 | private final int rowSpan;
45 |
46 | public SpannableItemEntry(int startLane, int anchorLane, int colSpan, int rowSpan) {
47 | super(startLane, anchorLane);
48 | this.colSpan = colSpan;
49 | this.rowSpan = rowSpan;
50 | }
51 |
52 | public SpannableItemEntry(Parcel in) {
53 | super(in);
54 | this.colSpan = in.readInt();
55 | this.rowSpan = in.readInt();
56 | }
57 |
58 | @Override
59 | public void writeToParcel(Parcel out, int flags) {
60 | super.writeToParcel(out, flags);
61 | out.writeInt(colSpan);
62 | out.writeInt(rowSpan);
63 | }
64 |
65 | public static final Parcelable.Creator CREATOR
66 | = new Parcelable.Creator() {
67 | @Override
68 | public SpannableItemEntry createFromParcel(Parcel in) {
69 | return new SpannableItemEntry(in);
70 | }
71 |
72 | @Override
73 | public SpannableItemEntry[] newArray(int size) {
74 | return new SpannableItemEntry[size];
75 | }
76 | };
77 | }
78 |
79 | private boolean mMeasuring;
80 |
81 | public SpannableGridLayoutManager(Context context) {
82 | this(context, null);
83 | }
84 |
85 | public SpannableGridLayoutManager(Context context, AttributeSet attrs) {
86 | this(context, attrs, 0);
87 | }
88 |
89 | public SpannableGridLayoutManager(Context context, AttributeSet attrs, int defStyle) {
90 | super(context, attrs, defStyle, DEFAULT_NUM_COLS, DEFAULT_NUM_ROWS);
91 | }
92 |
93 | public SpannableGridLayoutManager(Orientation orientation, int numColumns, int numRows) {
94 | super(orientation, numColumns, numRows);
95 | }
96 |
97 | private static int getLaneSpan(LayoutParams lp, boolean isVertical) {
98 | return (isVertical ? lp.colSpan : lp.rowSpan);
99 | }
100 |
101 | private static int getLaneSpan(SpannableItemEntry entry, boolean isVertical) {
102 | return (isVertical ? entry.colSpan : entry.rowSpan);
103 | }
104 |
105 | @Override
106 | public boolean canScrollHorizontally() {
107 | return super.canScrollHorizontally() && !mMeasuring;
108 | }
109 |
110 | @Override
111 | public boolean canScrollVertically() {
112 | return super.canScrollVertically() && !mMeasuring;
113 | }
114 |
115 | @Override
116 | public int getLaneSpanForChild(View child) {
117 | return getLaneSpan((LayoutParams) child.getLayoutParams(), isVertical());
118 | }
119 |
120 | @Override
121 | public int getLaneSpanForPosition(int position) {
122 | final SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(position);
123 | if (entry == null) {
124 | // add by zhousuqiang
125 | View view = getChildAt(position - getFirstVisiblePosition());
126 | if(null != view) {
127 | return getLaneSpanForChild(view);
128 | }
129 | throw new IllegalStateException("Could not find span for position " + position);
130 | }
131 |
132 | return getLaneSpan(entry, isVertical());
133 | }
134 |
135 | // add by zhousuqiang
136 | // public int getColSpanForPosition(int position) {
137 | // final SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(position);
138 | // if (entry == null) {
139 | // View view = getChildAt(position - getFirstVisiblePosition());
140 | // if(null != view) {
141 | // return getLaneSpan(entry, !isVertical());
142 | // }
143 | // throw new IllegalStateException("Could not find span for position " + position);
144 | // }
145 | //
146 | // return getLaneSpan(entry, !isVertical());
147 | // }
148 |
149 | @Override
150 | public void getLaneForPosition(LaneInfo outInfo, int position, Direction direction) {
151 | final SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(position);
152 | if (entry != null) {
153 | outInfo.set(entry.startLane, entry.anchorLane);
154 | return;
155 | }
156 |
157 | outInfo.setUndefined();
158 | }
159 |
160 | @Override
161 | protected void getLaneForChild(LaneInfo outInfo, View child, Direction direction) {
162 | super.getLaneForChild(outInfo, child, direction);
163 | if (outInfo.isUndefined()) {
164 | getLanes().findLane(outInfo, getLaneSpanForChild(child), direction);
165 | }
166 | }
167 |
168 | private int getChildWidth(int colSpan) {
169 | return (getLanes().getLaneSize()) * colSpan;
170 | }
171 |
172 | private int getChildHeight(int rowSpan) {
173 | return (getLanes().getLaneSize()) * rowSpan;
174 | }
175 |
176 | private int getWidthUsed(View child) {
177 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
178 | return getWidth() - getPaddingLeft() - getPaddingRight() - getChildWidth(lp.colSpan);
179 | }
180 |
181 | private int getHeightUsed(View child) {
182 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
183 | return getHeight() - getPaddingTop() - getPaddingBottom() - getChildHeight(lp.rowSpan);
184 | }
185 |
186 | @Override
187 | protected void measureChildWithMargins(View child) {
188 | // XXX: This will disable scrolling while measuring this child to ensure that
189 | // both width and height can use MATCH_PARENT properly.
190 | mMeasuring = true;
191 |
192 | final int laneCount = getLanes().getCount();
193 |
194 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
195 | int decorated = 30;
196 | int laneSize;
197 | if (isVertical()) {
198 | int parentWidth = getWidth() - getPaddingLeft() - getPaddingRight() - decorated * (laneCount -1);
199 | laneSize = parentWidth / laneCount;
200 | } else {
201 | int parentHeight = getHeight() - getPaddingTop() - getPaddingBottom() - decorated * (laneCount -1);
202 | laneSize = parentHeight / laneCount;
203 | }
204 | // lp.width = laneSize * lp.colSpan + decorated * (lp.colSpan - 1);
205 | // lp.height = laneSize * lp.rowSpan + decorated * (lp.rowSpan - 1);
206 |
207 | getLaneForChild(mTempLaneInfo, child, Direction.END);
208 | final int lane = mTempLaneInfo.startLane;
209 | final int laneSpan = getLaneSpan(lp, isVertical());
210 |
211 | // final int itemCount = getItemCount();
212 |
213 | // final boolean isVertical = isVertical();
214 |
215 | // final boolean firstLane = (lane == 0);
216 | // final boolean secondLane = isSecondLane(lm, itemPosition, lane);
217 |
218 | final boolean lastLane = (lane + laneSpan == laneCount);
219 | // final boolean beforeLastLane = (lane + laneSpan == laneCount - 1);
220 |
221 | // if(!lastLane) {
222 | // lp.height = laneSize * lp.rowSpan + decorated * (lp.rowSpan - 1);
223 | // }
224 |
225 | measureChildWithMargins(child, getWidthUsed(child), getHeightUsed(child));
226 | mMeasuring = false;
227 | }
228 |
229 | @Override
230 | protected void moveLayoutToPosition(int position, int offset, Recycler recycler, State state) {
231 | final boolean isVertical = isVertical();
232 | final Lanes lanes = getLanes();
233 |
234 | lanes.reset(0);
235 |
236 | for (int i = 0; i <= position; i++) {
237 | SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(i);
238 | if (entry == null) {
239 | final View child = recycler.getViewForPosition(i);
240 | entry = (SpannableItemEntry) cacheChildLaneAndSpan(child, Direction.END);
241 | }
242 |
243 | mTempLaneInfo.set(entry.startLane, entry.anchorLane);
244 |
245 | // The lanes might have been invalidated because an added or
246 | // removed item. See BaseLayoutManager.invalidateItemLanes().
247 | if (mTempLaneInfo.isUndefined()) {
248 | lanes.findLane(mTempLaneInfo, getLaneSpanForPosition(i), Direction.END);
249 | entry.setLane(mTempLaneInfo);
250 | }
251 |
252 | lanes.getChildFrame(mTempRect, getChildWidth(entry.colSpan),
253 | getChildHeight(entry.rowSpan), mTempLaneInfo, Direction.END);
254 |
255 | if (i != position) {
256 | pushChildFrame(entry, mTempRect, entry.startLane, getLaneSpan(entry, isVertical),
257 | Direction.END);
258 | }
259 | }
260 |
261 | lanes.getLane(mTempLaneInfo.startLane, mTempRect);
262 | lanes.reset(Direction.END);
263 | lanes.offset(offset - (isVertical ? mTempRect.bottom : mTempRect.right));
264 | }
265 |
266 | @Override
267 | protected ItemEntry cacheChildLaneAndSpan(View child, Direction direction) {
268 | final int position = getPosition(child);
269 |
270 | mTempLaneInfo.setUndefined();
271 |
272 | SpannableItemEntry entry = (SpannableItemEntry) getItemEntryForPosition(position);
273 | if (entry != null) {
274 | mTempLaneInfo.set(entry.startLane, entry.anchorLane);
275 | }
276 |
277 | if (mTempLaneInfo.isUndefined()) {
278 | getLaneForChild(mTempLaneInfo, child, direction);
279 | }
280 |
281 | if (entry == null) {
282 | final LayoutParams lp = (LayoutParams) child.getLayoutParams();
283 | entry = new SpannableItemEntry(mTempLaneInfo.startLane, mTempLaneInfo.anchorLane,
284 | lp.colSpan, lp.rowSpan);
285 | setItemEntryForPosition(position, entry);
286 | } else {
287 | entry.setLane(mTempLaneInfo);
288 | }
289 |
290 | return entry;
291 | }
292 |
293 | @Override
294 | public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
295 | super.checkLayoutParams(lp);
296 | if (lp.width != LayoutParams.MATCH_PARENT ||
297 | lp.height != LayoutParams.MATCH_PARENT) {
298 | return false;
299 | }
300 |
301 | if (lp instanceof LayoutParams) {
302 | final LayoutParams spannableLp = (LayoutParams) lp;
303 |
304 | if (isVertical()) {
305 | return (spannableLp.rowSpan >= 1 && spannableLp.colSpan >= 1 &&
306 | spannableLp.colSpan <= getLaneCount());
307 | } else {
308 | return (spannableLp.colSpan >= 1 && spannableLp.rowSpan >= 1 &&
309 | spannableLp.rowSpan <= getLaneCount());
310 | }
311 | }
312 |
313 | return false;
314 | }
315 |
316 | @Override
317 | public LayoutParams generateDefaultLayoutParams() {
318 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
319 | }
320 |
321 | @Override
322 | public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
323 | final LayoutParams spannableLp = new LayoutParams((MarginLayoutParams) lp);
324 | spannableLp.width = LayoutParams.MATCH_PARENT;
325 | spannableLp.height = LayoutParams.MATCH_PARENT;
326 |
327 | if (lp instanceof LayoutParams) {
328 | final LayoutParams other = (LayoutParams) lp;
329 | if (isVertical()) {
330 | spannableLp.colSpan = Math.max(1, Math.min(other.colSpan, getLaneCount()));
331 | spannableLp.rowSpan = Math.max(1, other.rowSpan);
332 | } else {
333 | spannableLp.colSpan = Math.max(1, other.colSpan);
334 | spannableLp.rowSpan = Math.max(1, Math.min(other.rowSpan, getLaneCount()));
335 | }
336 | }
337 |
338 | return spannableLp;
339 | }
340 |
341 | @Override
342 | public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
343 | return new LayoutParams(c, attrs);
344 | }
345 |
346 | public static class LayoutParams extends TvRecyclerView.LayoutParams {
347 | private static final int DEFAULT_SPAN = 1;
348 |
349 | public int rowSpan;
350 | public int colSpan;
351 |
352 | public LayoutParams(int width, int height) {
353 | super(width, height);
354 | rowSpan = DEFAULT_SPAN;
355 | colSpan = DEFAULT_SPAN;
356 | }
357 |
358 | public LayoutParams(Context c, AttributeSet attrs) {
359 | super(c, attrs);
360 |
361 | TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView_SpannableGridViewChild);
362 | colSpan = Math.max(
363 | DEFAULT_SPAN, a.getInt(R.styleable.TvRecyclerView_SpannableGridViewChild_colSpan, -1));
364 | rowSpan = Math.max(
365 | DEFAULT_SPAN, a.getInt(R.styleable.TvRecyclerView_SpannableGridViewChild_rowSpan, -1));
366 | a.recycle();
367 | }
368 |
369 | public LayoutParams(ViewGroup.LayoutParams other) {
370 | super(other);
371 | init(other);
372 | }
373 |
374 | public LayoutParams(MarginLayoutParams other) {
375 | super(other);
376 | init(other);
377 | }
378 |
379 | private void init(ViewGroup.LayoutParams other) {
380 | if (other instanceof LayoutParams) {
381 | final LayoutParams lp = (LayoutParams) other;
382 | rowSpan = lp.rowSpan;
383 | colSpan = lp.colSpan;
384 | } else {
385 | rowSpan = DEFAULT_SPAN;
386 | colSpan = DEFAULT_SPAN;
387 | }
388 | }
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/StaggeredGridLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.widget;
18 |
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.graphics.Rect;
22 | import android.os.Parcel;
23 | import android.os.Parcelable;
24 | import android.support.v7.widget.RecyclerView;
25 | import android.support.v7.widget.RecyclerView.Recycler;
26 | import android.support.v7.widget.RecyclerView.State;
27 | import android.util.AttributeSet;
28 | import android.view.View;
29 | import android.view.ViewGroup;
30 |
31 | import com.owen.tvrecyclerview.BaseLayoutManager;
32 | import com.owen.tvrecyclerview.Lanes;
33 | import com.owen.tvrecyclerview.Lanes.LaneInfo;
34 | import com.owen.tvrecyclerview.R;
35 |
36 |
37 | public class StaggeredGridLayoutManager extends GridLayoutManager {
38 | private static final String LOGTAG = "StaggeredGridLayoutManager";
39 |
40 | private static final int DEFAULT_NUM_COLS = 2;
41 | private static final int DEFAULT_NUM_ROWS = 2;
42 |
43 | protected static class StaggeredItemEntry extends BaseLayoutManager.ItemEntry {
44 | private final int span;
45 | private int width;
46 | private int height;
47 |
48 | public StaggeredItemEntry(int startLane, int anchorLane, int span) {
49 | super(startLane, anchorLane);
50 | this.span = span;
51 | }
52 |
53 | public StaggeredItemEntry(Parcel in) {
54 | super(in);
55 | this.span = in.readInt();
56 | this.width = in.readInt();
57 | this.height = in.readInt();
58 | }
59 |
60 | @Override
61 | public void writeToParcel(Parcel out, int flags) {
62 | super.writeToParcel(out, flags);
63 | out.writeInt(span);
64 | out.writeInt(width);
65 | out.writeInt(height);
66 | }
67 |
68 | public static final Parcelable.Creator CREATOR
69 | = new Parcelable.Creator() {
70 | @Override
71 | public StaggeredItemEntry createFromParcel(Parcel in) {
72 | return new StaggeredItemEntry(in);
73 | }
74 |
75 | @Override
76 | public StaggeredItemEntry[] newArray(int size) {
77 | return new StaggeredItemEntry[size];
78 | }
79 | };
80 | }
81 |
82 | public StaggeredGridLayoutManager(Context context) {
83 | this(context, null);
84 | }
85 |
86 | public StaggeredGridLayoutManager(Context context, AttributeSet attrs) {
87 | this(context, attrs, 0);
88 | }
89 |
90 | public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyle) {
91 | super(context, attrs, defStyle, DEFAULT_NUM_COLS, DEFAULT_NUM_ROWS);
92 | }
93 |
94 | public StaggeredGridLayoutManager(Orientation orientation, int numColumns, int numRows) {
95 | super(orientation, numColumns, numRows);
96 | }
97 |
98 | @Override
99 | public int getLaneSpanForChild(View child) {
100 | LayoutParams lp = (LayoutParams) child.getLayoutParams();
101 | return lp.span;
102 | }
103 |
104 | @Override
105 | public int getLaneSpanForPosition(int position) {
106 | final StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(position);
107 | if (entry == null) {
108 | View view = getChildAt(position - getFirstVisiblePosition());
109 | if(null != view) {
110 | return getLaneSpanForChild(view);
111 | }
112 | throw new IllegalStateException("Could not find span for position " + position);
113 | }
114 |
115 | return entry.span;
116 | }
117 |
118 | @Override
119 | public void getLaneForPosition(LaneInfo outInfo, int position, Direction direction) {
120 | final StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(position);
121 | if (entry != null) {
122 | outInfo.set(entry.startLane, entry.anchorLane);
123 | return;
124 | }
125 |
126 | outInfo.setUndefined();
127 | }
128 |
129 | @Override
130 | public void getLaneForChild(LaneInfo outInfo, View child, Direction direction) {
131 | super.getLaneForChild(outInfo, child, direction);
132 | if (outInfo.isUndefined()) {
133 | getLanes().findLane(outInfo, getLaneSpanForChild(child), direction);
134 | }
135 | }
136 |
137 | @Override
138 | protected void moveLayoutToPosition(int position, int offset, Recycler recycler, State state) {
139 | final boolean isVertical = isVertical();
140 | final Lanes lanes = getLanes();
141 |
142 | lanes.reset(0);
143 |
144 | for (int i = 0; i <= position; i++) {
145 | StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(i);
146 |
147 | if (entry != null) {
148 | mTempLaneInfo.set(entry.startLane, entry.anchorLane);
149 |
150 | // The lanes might have been invalidated because an added or
151 | // removed item. See BaseLayoutManager.invalidateItemLanes().
152 | if (mTempLaneInfo.isUndefined()) {
153 | lanes.findLane(mTempLaneInfo, getLaneSpanForPosition(i), Direction.END);
154 | entry.setLane(mTempLaneInfo);
155 | }
156 |
157 | lanes.getChildFrame(mTempRect, entry.width, entry.height, mTempLaneInfo,
158 | Direction.END);
159 | } else {
160 | final View child = recycler.getViewForPosition(i);
161 |
162 | // XXX: This might potentially cause stalls in the main
163 | // thread if the layout ends up having to measure tons of
164 | // child views. We might need to add different policies based
165 | // on known assumptions regarding certain layouts e.g. child
166 | // views have stable aspect ratio, lane size is fixed, etc.
167 | measureChild(child, Direction.END);
168 |
169 | // The measureChild() call ensures an entry is created for
170 | // this position.
171 | entry = (StaggeredItemEntry) getItemEntryForPosition(i);
172 |
173 | mTempLaneInfo.set(entry.startLane, entry.anchorLane);
174 | lanes.getChildFrame(mTempRect, getDecoratedMeasuredWidth(child),
175 | getDecoratedMeasuredHeight(child), mTempLaneInfo, Direction.END);
176 |
177 | cacheItemFrame(entry, mTempRect);
178 | }
179 |
180 | if (i != position) {
181 | pushChildFrame(entry, mTempRect, entry.startLane, entry.span, Direction.END);
182 | }
183 | }
184 |
185 | lanes.getLane(mTempLaneInfo.startLane, mTempRect);
186 | lanes.reset(Direction.END);
187 | lanes.offset(offset - (isVertical ? mTempRect.bottom : mTempRect.right));
188 | }
189 |
190 | @Override
191 | protected ItemEntry cacheChildLaneAndSpan(View child, Direction direction) {
192 | final int position = getPosition(child);
193 |
194 | mTempLaneInfo.setUndefined();
195 |
196 | StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(position);
197 | if (entry != null) {
198 | mTempLaneInfo.set(entry.startLane, entry.anchorLane);
199 | }
200 |
201 | if (mTempLaneInfo.isUndefined()) {
202 | getLaneForChild(mTempLaneInfo, child, direction);
203 | }
204 |
205 | if (entry == null) {
206 | entry = new StaggeredItemEntry(mTempLaneInfo.startLane, mTempLaneInfo.anchorLane,
207 | getLaneSpanForChild(child));
208 | setItemEntryForPosition(position, entry);
209 | } else {
210 | entry.setLane(mTempLaneInfo);
211 | }
212 |
213 | return entry;
214 | }
215 |
216 | void cacheItemFrame(StaggeredItemEntry entry, Rect childFrame) {
217 | entry.width = childFrame.right - childFrame.left;
218 | entry.height = childFrame.bottom - childFrame.top;
219 | }
220 |
221 | @Override
222 | protected ItemEntry cacheChildFrame(View child, Rect childFrame) {
223 | StaggeredItemEntry entry = (StaggeredItemEntry) getItemEntryForPosition(getPosition(child));
224 | if (entry == null) {
225 | throw new IllegalStateException("Tried to cache frame on undefined item");
226 | }
227 |
228 | cacheItemFrame(entry, childFrame);
229 | return entry;
230 | }
231 |
232 | @Override
233 | public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
234 | boolean result = super.checkLayoutParams(lp);
235 | if (lp instanceof LayoutParams) {
236 | final LayoutParams staggeredLp = (LayoutParams) lp;
237 | result &= (staggeredLp.span >= 1 && staggeredLp.span <= getLaneCount());
238 | }
239 |
240 | return result;
241 | }
242 |
243 | @Override
244 | public LayoutParams generateDefaultLayoutParams() {
245 | if (isVertical()) {
246 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
247 | } else {
248 | return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
249 | }
250 | }
251 |
252 | @Override
253 | public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
254 | final LayoutParams staggeredLp = new LayoutParams((ViewGroup.MarginLayoutParams) lp);
255 | if (isVertical()) {
256 | staggeredLp.width = LayoutParams.MATCH_PARENT;
257 | staggeredLp.height = lp.height;
258 | } else {
259 | staggeredLp.width = lp.width;
260 | staggeredLp.height = LayoutParams.MATCH_PARENT;
261 | }
262 |
263 | if (lp instanceof LayoutParams) {
264 | final LayoutParams other = (LayoutParams) lp;
265 | staggeredLp.span = Math.max(1, Math.min(other.span, getLaneCount()));
266 | }
267 |
268 | return staggeredLp;
269 | }
270 |
271 | @Override
272 | public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
273 | return new LayoutParams(c, attrs);
274 | }
275 |
276 | public static class LayoutParams extends TvRecyclerView.LayoutParams {
277 | private static final int DEFAULT_SPAN = 1;
278 |
279 | public int span;
280 |
281 | public LayoutParams(int width, int height) {
282 | super(width, height);
283 | span = DEFAULT_SPAN;
284 | }
285 |
286 | public LayoutParams(Context c, AttributeSet attrs) {
287 | super(c, attrs);
288 |
289 | TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView_StaggeredGridViewChild);
290 | span = Math.max(DEFAULT_SPAN, a.getInt(R.styleable.TvRecyclerView_StaggeredGridViewChild_span, -1));
291 | a.recycle();
292 | }
293 |
294 | public LayoutParams(ViewGroup.LayoutParams other) {
295 | super(other);
296 | init(other);
297 | }
298 |
299 | public LayoutParams(ViewGroup.MarginLayoutParams other) {
300 | super(other);
301 | init(other);
302 | }
303 |
304 | private void init(ViewGroup.LayoutParams other) {
305 | if (other instanceof LayoutParams) {
306 | final LayoutParams lp = (LayoutParams) other;
307 | span = lp.span;
308 | } else {
309 | span = DEFAULT_SPAN;
310 | }
311 | }
312 | }
313 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/owen/tvrecyclerview/widget/TvRecyclerView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Lucas Rocha
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.owen.tvrecyclerview.widget;
18 |
19 | import android.content.Context;
20 | import android.content.res.TypedArray;
21 | import android.graphics.Rect;
22 | import android.os.Build;
23 | import android.support.v4.view.ViewCompat;
24 | import android.support.v7.widget.LinearLayoutManager;
25 | import android.support.v7.widget.RecyclerView;
26 | import android.text.TextUtils;
27 | import android.util.AttributeSet;
28 | import android.util.Log;
29 | import android.view.FocusFinder;
30 | import android.view.KeyEvent;
31 | import android.view.View;
32 |
33 | import com.owen.tvrecyclerview.BaseLayoutManager;
34 | import com.owen.tvrecyclerview.R;
35 | import com.owen.tvrecyclerview.TwoWayLayoutManager;
36 |
37 | import java.lang.reflect.Constructor;
38 |
39 | public class TvRecyclerView extends RecyclerView {
40 | private static final String LOGTAG = TvRecyclerView.class.getSimpleName();
41 | private static final int DEFAULT_SELECTED_ITEM_OFFSET = 40;
42 |
43 | private int mVerticalSpacingWithMargins = 0;
44 | private int mHorizontalSpacingWithMargins = 0;
45 |
46 | private int mSelectedItemOffsetStart;
47 | private int mSelectedItemOffsetEnd;
48 |
49 | private boolean mSelectedItemCentered;
50 | private boolean mIsBaseLayoutManager;
51 |
52 | private int mScrollState = SCROLL_STATE_IDLE;
53 | private OnItemListener mOnItemListener;
54 |
55 | private ItemListener mItemListener;
56 |
57 | private static final Class>[] sConstructorSignature = new Class[] {
58 | Context.class, AttributeSet.class};
59 |
60 | private final Object[] sConstructorArgs = new Object[2];
61 |
62 |
63 | public TvRecyclerView(Context context) {
64 | this(context, null);
65 | }
66 |
67 | public TvRecyclerView(Context context, AttributeSet attrs) {
68 | this(context, attrs, 0);
69 | }
70 |
71 | public TvRecyclerView(Context context, AttributeSet attrs, int defStyle) {
72 | super(context, attrs, defStyle);
73 |
74 | init(context);
75 |
76 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TvRecyclerView, defStyle, 0);
77 |
78 | final String name = a.getString(R.styleable.TvRecyclerView_tv_layoutManager);
79 | if (!TextUtils.isEmpty(name)) {
80 | loadLayoutManagerFromName(context, attrs, name);
81 | }
82 | mSelectedItemCentered = a.getBoolean(R.styleable.TvRecyclerView_selectedItemisCentered, false);
83 | mSelectedItemOffsetStart = a.getInt(R.styleable.TvRecyclerView_selectedItemOffsetStart, DEFAULT_SELECTED_ITEM_OFFSET);
84 | mSelectedItemOffsetEnd = a.getInt(R.styleable.TvRecyclerView_selectedItemOffsetEnd, DEFAULT_SELECTED_ITEM_OFFSET);
85 |
86 | a.recycle();
87 | }
88 |
89 | private void init(Context context){
90 | setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
91 | setChildrenDrawingOrderEnabled(true);
92 | setWillNotDraw(true); // 自身不作onDraw处理
93 | setHasFixedSize(true);
94 | setOverScrollMode(View.OVER_SCROLL_NEVER);
95 |
96 | setClipChildren(false);
97 | setClipToPadding(false);
98 |
99 | setClickable(false);
100 | setFocusable(true);
101 | setFocusableInTouchMode(true);
102 |
103 | mItemListener = new ItemListener() {
104 | /**
105 | * 子控件的点击事件
106 | * @param itemView
107 | */
108 | @Override
109 | public void onClick(View itemView) {
110 | if(null != mOnItemListener) {
111 | mOnItemListener.onItemClick(TvRecyclerView.this, itemView, getChildLayoutPosition(itemView));
112 | }
113 | }
114 |
115 | /**
116 | * 子控件的焦点变动事件
117 | * @param itemView
118 | * @param hasFocus
119 | */
120 | @Override
121 | public void onFocusChange(View itemView, boolean hasFocus) {
122 | if(null != mOnItemListener) {
123 | if(null != itemView) {
124 | itemView.setSelected(hasFocus);
125 | if (hasFocus) {
126 | mOnItemListener.onItemSelected(TvRecyclerView.this, itemView, getChildLayoutPosition(itemView));
127 | } else {
128 | mOnItemListener.onItemPreSelected(TvRecyclerView.this, itemView, getChildLayoutPosition(itemView));
129 | }
130 | }
131 | }
132 | }
133 | };
134 | }
135 |
136 | private void loadLayoutManagerFromName(Context context, AttributeSet attrs, String name) {
137 | try {
138 | final int dotIndex = name.indexOf('.');
139 | if (dotIndex == -1) {
140 | name = "com.owen.tvrecyclerview.widget." + name;
141 | } else if (dotIndex == 0) {
142 | final String packageName = context.getPackageName();
143 | name = packageName + "." + name;
144 | }
145 |
146 | Class extends TwoWayLayoutManager> clazz =
147 | context.getClassLoader().loadClass(name).asSubclass(TwoWayLayoutManager.class);
148 |
149 | Constructor extends TwoWayLayoutManager> constructor =
150 | clazz.getConstructor(sConstructorSignature);
151 |
152 | sConstructorArgs[0] = context;
153 | sConstructorArgs[1] = attrs;
154 |
155 | setLayoutManager(constructor.newInstance(sConstructorArgs));
156 | } catch (Exception e) {
157 | throw new IllegalStateException("Could not load TwoWayLayoutManager from " +
158 | "class: " + name, e);
159 | }
160 | }
161 |
162 | @Override
163 | public void setLayoutManager(LayoutManager layout) {
164 | mIsBaseLayoutManager = layout instanceof BaseLayoutManager;
165 | super.setLayoutManager(layout);
166 | }
167 |
168 | /**
169 | * 设置选中的Item距离开始或结束的偏移量;
170 | * 与滚动方向有关;
171 | * 与setSelectedItemAtCentered()方法二选一
172 | * @param offsetStart
173 | * @param offsetEnd
174 | */
175 | public void setSelectedItemOffset(int offsetStart, int offsetEnd) {
176 | this.mSelectedItemOffsetStart = offsetStart;
177 | this.mSelectedItemOffsetEnd = offsetEnd;
178 | }
179 |
180 | /**
181 | * 设置选中的Item居中;
182 | * 与setSelectedItemOffset()方法二选一
183 | * @param isCentered
184 | */
185 | public void setSelectedItemAtCentered(boolean isCentered) {
186 | this.mSelectedItemCentered = isCentered;
187 | }
188 |
189 | private boolean isVertical() {
190 | if(mIsBaseLayoutManager) {
191 | BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
192 | return layout.isVertical();
193 | } else if (getLayoutManager() instanceof LinearLayoutManager) {
194 | LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();
195 | return layout.getOrientation() == LinearLayoutManager.VERTICAL;
196 | }
197 | return true;
198 | }
199 |
200 | private int getFreeSize() {
201 | if(!isVertical()) {
202 | return getFreeHeight();
203 | } else {
204 | return getFreeWidth();
205 | }
206 | }
207 |
208 | private int getFreeHeight() {
209 | return getHeight() - getPaddingTop() - getPaddingBottom();
210 | }
211 |
212 | private int getFreeWidth() {
213 | return getWidth() - getPaddingLeft() - getPaddingRight();
214 | }
215 |
216 | @Override
217 | public void requestChildFocus(View child, View focused) {
218 | if(null != child) {
219 | if (mSelectedItemCentered) {
220 | mSelectedItemOffsetStart = !isVertical() ? (getFreeWidth() - child.getWidth()) : (getFreeHeight() - child.getHeight());
221 | mSelectedItemOffsetStart /= 2;
222 | mSelectedItemOffsetEnd = mSelectedItemOffsetStart;
223 | }
224 | }
225 | super.requestChildFocus(child, focused);
226 | }
227 |
228 | @Override
229 | public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
230 | final int parentLeft = getPaddingLeft();
231 | final int parentTop = getPaddingTop();
232 | final int parentRight = getWidth() - getPaddingRight();
233 | final int parentBottom = getHeight() - getPaddingBottom();
234 | final int childLeft = child.getLeft() + rect.left - child.getScrollX();
235 | final int childTop = child.getTop() + rect.top - child.getScrollY();
236 | final int childRight = childLeft + rect.width();
237 | final int childBottom = childTop + rect.height();
238 |
239 | final int offScreenLeft = Math.min(0, childLeft - parentLeft - mSelectedItemOffsetStart);
240 | final int offScreenTop = Math.min(0, childTop - parentTop - mSelectedItemOffsetStart);
241 | final int offScreenRight = Math.max(0, childRight - parentRight + mSelectedItemOffsetEnd);
242 | final int offScreenBottom = Math.max(0, childBottom - parentBottom + mSelectedItemOffsetEnd);
243 |
244 | // Favor the "start" layout direction over the end when bringing one side or the other
245 | // of a large rect into view. If we decide to bring in end because start is already
246 | // visible, limit the scroll such that start won't go out of bounds.
247 | final int dx;
248 | if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
249 | dx = offScreenRight != 0 ? offScreenRight
250 | : Math.max(offScreenLeft, childRight - parentRight);
251 | } else {
252 | dx = offScreenLeft != 0 ? offScreenLeft
253 | : Math.min(childLeft - parentLeft, offScreenRight);
254 | }
255 |
256 | // Favor bringing the top into view over the bottom. If top is already visible and
257 | // we should scroll to make bottom visible, make sure top does not go out of bounds.
258 | final int dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom);
259 |
260 | if (dx != 0 || dy != 0) {
261 | if (immediate) {
262 | scrollBy(dx, dy);
263 | } else {
264 | smoothScrollBy(dx, dy);
265 | }
266 | if(!isVertical()) {
267 | if(dx == 0) {
268 | postInvalidate();
269 | }
270 | } else if (dy == 0) {
271 | postInvalidate();
272 | }
273 | return true;
274 | }
275 |
276 | // 重绘是为了选中item置顶,具体请参考getChildDrawingOrder方法
277 | postInvalidate();
278 | return false;
279 | }
280 |
281 | /**
282 | * 通过Margins来设置布局的横纵间距;
283 | * (与addItemDecoration()方法可二选一)
284 | * @param verticalSpacing
285 | * @param horizontalSpacing
286 | */
287 | public void setSpacingWithMargins(int verticalSpacing, int horizontalSpacing) {
288 | this.mVerticalSpacingWithMargins = verticalSpacing;
289 | this.mHorizontalSpacingWithMargins = horizontalSpacing;
290 | if(mIsBaseLayoutManager) {
291 | BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
292 | layout.setSpacingWithMargins(verticalSpacing, horizontalSpacing);
293 | }
294 | adjustPadding();
295 | }
296 |
297 | private void adjustPadding() {
298 | if((mVerticalSpacingWithMargins > 0 || mHorizontalSpacingWithMargins > 0)) {
299 | final int verticalSpacingHalf = mVerticalSpacingWithMargins / 2;
300 | final int horizontalSpacingHalf = mHorizontalSpacingWithMargins / 2;
301 | final int l = getPaddingLeft() - verticalSpacingHalf;
302 | final int t = getPaddingTop() - horizontalSpacingHalf;
303 | final int r = getPaddingRight() - verticalSpacingHalf;
304 | final int b = getPaddingBottom() - horizontalSpacingHalf;
305 | setPadding(l, t, r, b);
306 | }
307 | }
308 |
309 | public TwoWayLayoutManager.Orientation getOrientation() {
310 | if(mIsBaseLayoutManager) {
311 | BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
312 | return layout.getOrientation();
313 | } else {
314 | return TwoWayLayoutManager.Orientation.HORIZONTAL;
315 | }
316 | }
317 |
318 | public void setOrientation(TwoWayLayoutManager.Orientation orientation) {
319 | if(mIsBaseLayoutManager) {
320 | BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
321 | layout.setOrientation(orientation);
322 | }
323 | }
324 |
325 | public int getFirstVisiblePosition() {
326 | if(getChildCount() == 0)
327 | return 0;
328 | else
329 | return getChildLayoutPosition(getChildAt(0));
330 | }
331 |
332 | public int getLastVisiblePosition() {
333 | final int childCount = getChildCount();
334 | if(childCount == 0)
335 | return 0;
336 | else
337 | return getChildLayoutPosition(getChildAt(childCount - 1));
338 | }
339 |
340 | public void scrollToPositionWithOffset(int position) {
341 | if(mIsBaseLayoutManager) {
342 | BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
343 | layout.scrollToPositionWithOffset(position, mSelectedItemOffsetStart);
344 | return;
345 | }
346 | scrollToPosition(position);
347 | }
348 |
349 | // @Override
350 | // public void scrollToPosition(int position) {
351 | // if(mIsBaseLayoutManager) {
352 | // BaseLayoutManager layout = (BaseLayoutManager) getLayoutManager();
353 | // layout.scrollToPosition(position);
354 | // return;
355 | // }
356 | // super.scrollToPosition(position);
357 | // }
358 |
359 | int position = 0;
360 | @Override
361 | protected int getChildDrawingOrder(int childCount, int i) {
362 | View view = getFocusedChild();
363 | if(null != view) {
364 | position = getChildAdapterPosition(view) - getFirstVisiblePosition();
365 | if (position < 0) {
366 | return i;
367 | } else {
368 | if (i == childCount - 1) {//这是最后一个需要刷新的item
369 | if (position > i) {
370 | position = i;
371 | }
372 | return position;
373 | }
374 | if (i == position) {//这是原本要在最后一个刷新的item
375 | return childCount - 1;
376 | }
377 | }
378 | }
379 | return i;
380 | }
381 |
382 | @Override
383 | public void onScrollStateChanged(int state) {
384 | mScrollState = state;
385 | }
386 |
387 | public boolean isScrolling() {
388 | return mScrollState == SCROLL_STATE_SETTLING;
389 | }
390 |
391 | @Override
392 | public boolean dispatchKeyEvent(KeyEvent event) {
393 | switch (event.getAction()) {
394 | case KeyEvent.ACTION_DOWN:
395 | if(onKeyDown(event.getKeyCode(), event))
396 | return true;
397 | break;
398 | case KeyEvent.ACTION_UP:
399 | if(onKeyUp(event.getKeyCode(), event))
400 | return true;
401 | break;
402 | }
403 | return super.dispatchKeyEvent(event);
404 | }
405 |
406 | @Override
407 | public boolean onKeyDown(int keyCode, KeyEvent event) {
408 | int direction = -1;
409 | switch (keyCode){
410 | case KeyEvent.KEYCODE_DPAD_DOWN:
411 | direction = FOCUS_DOWN;
412 | break;
413 | case KeyEvent.KEYCODE_DPAD_RIGHT:
414 | direction = FOCUS_RIGHT;
415 | break;
416 | case KeyEvent.KEYCODE_DPAD_LEFT:
417 | direction = FOCUS_LEFT;
418 | break;
419 | case KeyEvent.KEYCODE_DPAD_UP:
420 | direction = FOCUS_UP;
421 | break;
422 | }
423 |
424 | if(direction == -1 || hasInBorder(direction)) {
425 | return false;
426 | } else {
427 | FocusFinder ff = FocusFinder.getInstance();
428 | View newFocusedView = ff.findNextFocus(this, getFocusedChild(), direction);
429 | if (null != newFocusedView) {
430 | newFocusedView.requestFocus();
431 | }
432 | }
433 | return true;
434 | }
435 |
436 | private boolean hasInBorder(int direction) {
437 | boolean result = false;
438 | final View view = getFocusedChild();
439 | if(null != view) {
440 | Rect outRect = new Rect();
441 | getLayoutManager().calculateItemDecorationsForChild(view, outRect);
442 | LayoutParams lp = (LayoutParams) view.getLayoutParams();
443 | switch (direction) {
444 | case FOCUS_DOWN:
445 | result = getHeight() - view.getBottom() <= getPaddingBottom() + lp.bottomMargin + outRect.bottom;
446 | if(isVertical()) {
447 | result = result && getLastVisiblePosition() == (getAdapter().getItemCount() - 1);
448 | }
449 | break;
450 | case FOCUS_UP:
451 | result = view.getTop() <= getPaddingTop() + lp.topMargin + outRect.top;
452 | if(isVertical()) {
453 | result = result && getFirstVisiblePosition() == 0;
454 | }
455 | break;
456 | case FOCUS_LEFT:
457 | result = view.getLeft() <= getPaddingLeft() + lp.leftMargin + outRect.left;
458 | if(!isVertical()) {
459 | result = result && getFirstVisiblePosition() == 0;
460 | }
461 | break;
462 | case FOCUS_RIGHT:
463 | result = getWidth() - view.getRight() <= getPaddingRight() + lp.rightMargin + outRect.right;
464 | if(!isVertical()) {
465 | result = result && getLastVisiblePosition() == (getAdapter().getItemCount() - 1);
466 | }
467 | break;
468 | }
469 | }
470 | return result;
471 | }
472 |
473 | @Override
474 | public void onChildAttachedToWindow(View child) {
475 | child.setOnClickListener(mItemListener);
476 | child.setOnFocusChangeListener(mItemListener);
477 | }
478 |
479 | @Override
480 | protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
481 | Log.e(LOGTAG, "onFocusChanged..." + gainFocus + " ,direction="+direction);
482 | super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
483 | }
484 |
485 | @Override
486 | public boolean hasFocus() {
487 | Log.e(LOGTAG, "hasFocus...");
488 | return super.hasFocus();
489 | }
490 |
491 | @Override
492 | public boolean isInTouchMode() {
493 | boolean result = super.isInTouchMode();
494 | // Log.e(LOGTAG, "isInTouchMode...result="+result);
495 | // 解决4.4版本抢焦点的问题
496 | if (Build.VERSION.SDK_INT == 19) {
497 | return !(hasFocus() && !result);
498 | } else {
499 | return result;
500 | }
501 | }
502 |
503 | private interface ItemListener extends View.OnClickListener, View.OnFocusChangeListener {
504 | }
505 |
506 | public interface OnItemListener {
507 | void onItemPreSelected(TvRecyclerView parent, View itemView, int position);
508 | void onItemSelected(TvRecyclerView parent, View itemView, int position);
509 | void onItemClick(TvRecyclerView parent, View itemView, int position);
510 | }
511 |
512 | public void setOnItemListener(OnItemListener onItemListener) {
513 | mOnItemListener = onItemListener;
514 | }
515 | }
516 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Library
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':example', ':library'
2 |
--------------------------------------------------------------------------------