├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── lishide
│ │ └── scrollrecyclerview
│ │ ├── AppBean.java
│ │ ├── AppBeanAdapter.java
│ │ └── StagGridActivity.java
│ └── res
│ ├── drawable-hdpi
│ └── home_apps_focused.9.png
│ ├── drawable
│ ├── home_apps_unfocused.xml
│ └── selector_home_apps.xml
│ ├── layout
│ ├── activity_stag_grid.xml
│ └── item_grid_apps.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── art
└── ScrollRecyclerView_art.gif
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── recyclerview-scroll-lib
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── lishide
│ │ └── recyclerview
│ │ └── scroll
│ │ ├── ScrollRecyclerView.java
│ │ ├── SpaceItemDecoration.java
│ │ └── listener
│ │ ├── OnItemClickListener.java
│ │ ├── OnItemKeyListener.java
│ │ ├── OnItemLongClickListener.java
│ │ └── OnItemSelectedListener.java
│ └── res
│ └── values
│ └── strings.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ScrollRecyclerView
2 | RecyclerView 横向 / 纵向滚动网格布局,适用于 Android 平板、Android TV 或其他定制化 Android 设备等,使用遥控器方向导航键控制列表滑动及 item 选择状态。
3 |
4 | ## 效果预览
5 | 
6 | ---
7 |
8 | [](https://jitpack.io/#lishide/ScrollRecyclerView)
9 | ## 依赖
10 | #### JitPack 引入方法
11 | ##### 1. 在 Project 下的 build.gradle 添加
12 | ```groovy
13 | allprojects {
14 | repositories {
15 | ...
16 | maven { url 'https://jitpack.io' }
17 | }
18 | }
19 | ```
20 |
21 | ##### 2. 在 Module 下的 build.gradle 添加
22 | ```groovy
23 | dependencies {
24 | compile 'com.github.lishide:ScrollRecyclerView:v1.0.0'
25 | }
26 | ```
27 |
28 | ## 使用
29 |
30 | * **在 xml 中引用 ScrollRecyclerView**
31 |
32 | ```xml
33 |
37 | ```
38 |
39 | * **初始化 ScrollRecyclerView,设置布局管理器、间距、适配器、数据等**
40 |
41 | ```java
42 | // 初始化 ScrollRecyclerView
43 | mScrollRecyclerView = (ScrollRecyclerView) findViewById(R.id.scroll_recycler_view);
44 | // 设置动画
45 | mScrollRecyclerView.setItemAnimator(new DefaultItemAnimator());
46 | // 设置布局管理器:瀑布流式
47 | mScrollRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,
48 | StaggeredGridLayoutManager.HORIZONTAL));
49 | // 根据需要设置间距等
50 | int right = (int) getResources().getDimension(R.dimen.dp_20);
51 | int bottom = (int) getResources().getDimension(R.dimen.dp_20);
52 | RecyclerView.ItemDecoration spacingInPixel = new SpaceItemDecoration(right, bottom);
53 | mScrollRecyclerView.addItemDecoration(spacingInPixel);
54 | ```
55 |
56 | * **适配器中初始化控件并设置数据**
57 |
58 | 使用的 Adapter 就是正常的 Adapter。为了简单明了,在示例中用的是最普通的一种。当然了,你完全可以使用你常用的或是被封装过的高级 Adapter。
59 |
60 | Adapter 中**比较重要的是**设置 itemView 可以获得焦点,并监听焦点变化。还有要设置 Tag,用来标记 item 的 position,后面有用。
61 |
62 | ```java
63 | holder.itemView.setFocusable(true);
64 | holder.itemView.setTag(position);
65 | holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
66 | @Override
67 | public void onFocusChange(View v, boolean hasFocus) {
68 |
69 | }
70 | });
71 | ```
72 |
73 | 会发现在 Adapter 中设置了许多监听器,目前有这四个:
74 | * **item 选定监听(OnItemSelectedListener)**
75 | * **item 点击监听(OnItemClickListener)**
76 | * **item 长按监听(OnItemLongClickListener)**
77 | * **遥控器其他按键监听(OnItemKeyListener)**
78 |
79 | 已将这四个 **Listener** 放在 lib 中,开发者根据需要直接设置和调用即可。
80 |
81 | **要想实现咱们今天主要实现的功能——使用遥控器方向导航键控制列表滑动及 item 选择状态,下面的步骤很重要。**
82 | * 在焦点监听器中,判断获得焦点时调用 `mOnItemSelectedListener.OnItemSelected(v, currentPosition);`,传入 view 和当前 position。
83 |
84 | ```java
85 | if (hasFocus) {
86 | currentPosition = (int) holder.itemView.getTag();
87 |
88 | mOnItemSelectedListener.OnItemSelected(v, currentPosition);
89 | }
90 | ```
91 | * **滑动列表**
92 |
93 | 设置 item 选定监听器,然后在监听器中实现列表滑动。
94 |
95 | ```java
96 | adapter.setOnItemSelectedListener(mOnItemSelectedListener);
97 | ```
98 | ```java
99 | OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
100 | @Override
101 | public void OnItemSelected(View view, int position) {
102 | mScrollRecyclerView.smoothHorizontalScrollToNext(position);
103 | }
104 | };
105 | ```
106 |
107 | > 实现滑动的功能就是这个`smoothHorizontalScrollToNext`方法了。具体的实现过程推荐下载 demo 看代码,demo 里面的注释非常全了。
108 |
109 | 好了,至此 **使用遥控器方向导航键控制列表滑动及 item 选择状态** 的功能大概完成了。顺便解释一下其他几个 Listener 的作用,OnItemClickListener、OnItemLongClickListener 这两个好理解,在其他的列表点击监听也会遇到过,就不过多说了。需要注意一点的是,item 长按监听要对 view 的点击事件返回值根据需要处理一下,比如 `return false` 会在长按事件结束后再触发一次点击事件,`return true`则只会触发长按事件。如果有特定需要,比如焦点在列表的某个 item 上时,按下了 OK 键,需要跳转到一个新的界面;或者按下 Menu 键做其他业务逻辑处理等等,此时应该设置监听——OnItemKeyListener(遥控器其他按键监听)。
110 |
111 | **ScrollRecyclerView 的用处有一些局限性,手机端应该是用不到(我感觉,因为有触摸屏~),主要适用于 Android 平板、Android TV 或其他定制化 Android 设备等......这里,仍期待得到您的支持!**
112 |
113 | **您在使用过程中,发现 bug 或有好的建议欢迎 issue、email (lishidezy@gmail.com),如果感觉对你有帮助也欢迎点个 star,留下点印记吧。**
114 |
115 |
116 | [1]: https://github.com/lishide/ScrollRecyclerView
117 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.3"
6 | defaultConfig {
7 | applicationId "com.lishide.scrollrecyclerview"
8 | minSdkVersion 15
9 | targetSdkVersion 29
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(include: ['*.jar'], dir: 'libs')
24 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
25 | implementation project(':recyclerview-scroll-lib')
26 | }
27 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lishide/scrollrecyclerview/AppBean.java:
--------------------------------------------------------------------------------
1 | package com.lishide.scrollrecyclerview;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | public class AppBean {
6 | private Drawable appIcon;
7 | private String appName;
8 | private int appSize;
9 | private boolean isSd = false;
10 | private boolean isSystem = false;
11 | private String appPackageName;
12 |
13 | public String getApkPath() {
14 | return apkPath;
15 | }
16 |
17 | public void setApkPath(String apkPath) {
18 | this.apkPath = apkPath;
19 | }
20 |
21 | private String apkPath;
22 |
23 | public String getAppPackageName() {
24 | return appPackageName;
25 | }
26 |
27 | public void setAppPackageName(String appPackageName) {
28 | this.appPackageName = appPackageName;
29 | }
30 |
31 | public Drawable getAppIcon() {
32 | return appIcon;
33 | }
34 |
35 | public void setAppIcon(Drawable appIcon) {
36 | this.appIcon = appIcon;
37 | }
38 |
39 | public String getAppName() {
40 | return appName;
41 | }
42 |
43 | public void setAppName(String appName) {
44 | this.appName = appName;
45 | }
46 |
47 | public int getAppSize() {
48 | return appSize;
49 | }
50 |
51 | public void setAppSize(int appSize) {
52 | this.appSize = appSize;
53 | }
54 |
55 | public boolean isSd() {
56 | return isSd;
57 | }
58 |
59 | public void setSd(boolean sd) {
60 | isSd = sd;
61 | }
62 |
63 | public boolean isSystem() {
64 | return isSystem;
65 | }
66 |
67 | public void setSystem(boolean system) {
68 | isSystem = system;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lishide/scrollrecyclerview/AppBeanAdapter.java:
--------------------------------------------------------------------------------
1 | package com.lishide.scrollrecyclerview;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import android.view.KeyEvent;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import androidx.recyclerview.widget.RecyclerView;
13 |
14 | import com.lishide.recyclerview.scroll.listener.OnItemClickListener;
15 | import com.lishide.recyclerview.scroll.listener.OnItemKeyListener;
16 | import com.lishide.recyclerview.scroll.listener.OnItemLongClickListener;
17 | import com.lishide.recyclerview.scroll.listener.OnItemSelectedListener;
18 |
19 | import java.util.List;
20 |
21 | public class AppBeanAdapter extends RecyclerView.Adapter {
22 | private static final String TAG = AppBeanAdapter.class.getSimpleName();
23 | private Context mContext;
24 | private List mList;
25 | private LayoutInflater inflater;
26 | private int currentPosition;
27 | private OnItemSelectedListener mOnItemSelectedListener;
28 | private OnItemClickListener mOnItemClickListener;
29 | private OnItemLongClickListener mOnItemLongClickListener;
30 | private OnItemKeyListener mOnItemKeyListener;
31 |
32 | public AppBeanAdapter(Context context, List list) {
33 | inflater = LayoutInflater.from(context);
34 | this.mContext = context;
35 | this.mList = list;
36 | }
37 |
38 | @Override
39 | public int getItemCount() {
40 | return mList == null ? 0 : mList.size();
41 | }
42 |
43 | @Override
44 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
45 | return new ViewHolder(inflater.inflate(R.layout.item_grid_apps, parent, false));
46 | }
47 |
48 | @Override
49 | public void onBindViewHolder(final ViewHolder holder, int position) {
50 | holder.mImageView.setImageDrawable(mList.get(position).getAppIcon());
51 | holder.mTextView.setText(mList.get(position).getAppName());
52 |
53 | // 设置 itemView 可以获得焦点
54 | holder.itemView.setFocusable(true);
55 | holder.itemView.setTag(position);
56 | holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
57 | @Override
58 | public void onFocusChange(View v, boolean hasFocus) {
59 | Log.d(TAG, "焦点监听器被调用了");
60 | Log.d(TAG, "hasFocus=" + hasFocus);
61 | if (hasFocus) {
62 | currentPosition = (int) holder.itemView.getTag();
63 | Log.d(TAG, "getTag=" + currentPosition);
64 | Log.i(TAG, "The view hasFocus=" + v + ", holder.itemView=" + holder.itemView);
65 | mOnItemSelectedListener.onItemSelected(v, currentPosition);
66 | }
67 | }
68 | });
69 |
70 | holder.itemView.setOnClickListener(new View.OnClickListener() {
71 | @Override
72 | public void onClick(View v) {
73 | mOnItemClickListener.onItemClick(v, currentPosition);
74 | }
75 | });
76 |
77 | holder.itemView.setOnKeyListener(new View.OnKeyListener() {
78 | @Override
79 | public boolean onKey(View v, int keyCode, KeyEvent event) {
80 | mOnItemKeyListener.onItemKey(v, keyCode, event, currentPosition);
81 | return false;
82 | }
83 | });
84 |
85 | holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
86 | @Override
87 | public boolean onLongClick(View v) {
88 | mOnItemLongClickListener.onItemLongClick(v, currentPosition);
89 | return true;
90 | }
91 | });
92 | }
93 |
94 | class ViewHolder extends RecyclerView.ViewHolder {
95 | ImageView mImageView;
96 | TextView mTextView;
97 |
98 | ViewHolder(View itemView) {
99 | super(itemView);
100 | mImageView = (ImageView) itemView.findViewById(R.id.iv_grid_item_icon);
101 | mTextView = (TextView) itemView.findViewById(R.id.tv_grid_item_name);
102 | }
103 | }
104 |
105 | public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
106 | this.mOnItemSelectedListener = onItemSelectedListener;
107 | }
108 |
109 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
110 | this.mOnItemClickListener = onItemClickListener;
111 | }
112 |
113 | public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
114 | this.mOnItemLongClickListener = onItemLongClickListener;
115 | }
116 |
117 | public void setOnItemKeyListener(OnItemKeyListener onItemKeyListener) {
118 | this.mOnItemKeyListener = onItemKeyListener;
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lishide/scrollrecyclerview/StagGridActivity.java:
--------------------------------------------------------------------------------
1 | package com.lishide.scrollrecyclerview;
2 |
3 | import android.content.pm.ApplicationInfo;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 | import android.view.KeyEvent;
9 | import android.view.View;
10 |
11 | import androidx.appcompat.app.AppCompatActivity;
12 | import androidx.recyclerview.widget.DefaultItemAnimator;
13 | import androidx.recyclerview.widget.RecyclerView;
14 | import androidx.recyclerview.widget.StaggeredGridLayoutManager;
15 |
16 | import com.lishide.recyclerview.scroll.ScrollRecyclerView;
17 | import com.lishide.recyclerview.scroll.SpaceItemDecoration;
18 | import com.lishide.recyclerview.scroll.listener.OnItemClickListener;
19 | import com.lishide.recyclerview.scroll.listener.OnItemKeyListener;
20 | import com.lishide.recyclerview.scroll.listener.OnItemLongClickListener;
21 | import com.lishide.recyclerview.scroll.listener.OnItemSelectedListener;
22 |
23 | import java.io.File;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | /**
28 | * 横向网格布局示例
29 | * 数据源为设备中所有 App
30 | * 演示 item 选定监听、item 点击监听、item 长按监听、遥控器其他按键监听
31 | */
32 | public class StagGridActivity extends AppCompatActivity {
33 | private static final String TAG = StagGridActivity.class.getSimpleName();
34 | private ScrollRecyclerView mScrollRecyclerView;
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_stag_grid);
40 | // 初始化 ScrollRecyclerView
41 | mScrollRecyclerView = (ScrollRecyclerView) findViewById(R.id.scroll_recycler_view);
42 | // 获得数据,数据源为设备中所有 App
43 | List mList = getAllApk();
44 | // 初始化适配器
45 | AppBeanAdapter adapter = new AppBeanAdapter(this, mList);
46 | // 设置动画
47 | mScrollRecyclerView.setItemAnimator(new DefaultItemAnimator());
48 | // 设置布局管理器:瀑布流式
49 | mScrollRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,
50 | StaggeredGridLayoutManager.HORIZONTAL));
51 | // 根据需要设置间距等
52 | int right = (int) getResources().getDimension(R.dimen.dp_20);
53 | int bottom = (int) getResources().getDimension(R.dimen.dp_20);
54 | RecyclerView.ItemDecoration spacingInPixel = new SpaceItemDecoration(right, bottom);
55 | mScrollRecyclerView.addItemDecoration(spacingInPixel);
56 | // 关联适配器
57 | mScrollRecyclerView.setAdapter(adapter);
58 | adapter.setOnItemSelectedListener(mOnItemSelectedListener);
59 | adapter.setOnItemClickListener(mOnItemClickListener);
60 | adapter.setOnItemLongClickListener(mOnItemLongClickListener);
61 | adapter.setOnItemKeyListener(mOnItemKeyListener);
62 | }
63 |
64 | public List getAllApk() {
65 | List appBeanList = new ArrayList<>();
66 | AppBean bean;
67 | PackageManager packageManager = getPackageManager();
68 | List list = packageManager.getInstalledPackages(0);
69 | for (PackageInfo p : list) {
70 | bean = new AppBean();
71 | bean.setAppIcon(p.applicationInfo.loadIcon(packageManager));
72 | bean.setAppName(packageManager.getApplicationLabel(p.applicationInfo).toString());
73 | bean.setAppPackageName(p.applicationInfo.packageName);
74 | bean.setApkPath(p.applicationInfo.sourceDir);
75 | File file = new File(p.applicationInfo.sourceDir);
76 | bean.setAppSize((int) file.length());
77 | int flags = p.applicationInfo.flags;
78 | //判断是否是属于系统的apk
79 | if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
80 | bean.setSystem(true);
81 | } else {
82 | bean.setSd(true);
83 | }
84 | appBeanList.add(bean);
85 | }
86 | return appBeanList;
87 | }
88 |
89 | @Override
90 | public boolean onKeyDown(int keyCode, KeyEvent event) {
91 | switch (keyCode) {
92 | case KeyEvent.KEYCODE_DPAD_LEFT:
93 | Log.d(TAG, "按下导航键<-左键->");
94 | break;
95 | case KeyEvent.KEYCODE_DPAD_RIGHT:
96 | Log.d(TAG, "按下导航键<-右键->");
97 | break;
98 | case KeyEvent.KEYCODE_DPAD_UP:
99 | Log.d(TAG, "按下导航键<-上键->");
100 | break;
101 | case KeyEvent.KEYCODE_DPAD_DOWN:
102 | Log.d(TAG, "按下导航键<-下键->");
103 | break;
104 | default:
105 | break;
106 | }
107 | return super.onKeyDown(keyCode, event);
108 | }
109 |
110 | OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
111 | @Override
112 | public void onItemSelected(View view, int position) {
113 | mScrollRecyclerView.smoothHorizontalScrollToNext(position);
114 | }
115 | };
116 |
117 | OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
118 | @Override
119 | public void onItemClick(View view, int position) {
120 | Log.d(TAG, "<--1--> click position = " + position);
121 | }
122 | };
123 |
124 | OnItemLongClickListener mOnItemLongClickListener = new OnItemLongClickListener() {
125 | @Override
126 | public void onItemLongClick(View view, int position) {
127 | Log.d(TAG, "<--2--> Long click position = " + position);
128 | }
129 | };
130 |
131 | OnItemKeyListener mOnItemKeyListener = new OnItemKeyListener() {
132 | @Override
133 | public void onItemKey(View view, int keyCode, KeyEvent event, int position) {
134 | if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
135 | Log.d(TAG, "KEYCODE_DPAD_CENTER");
136 | } else if (keyCode == KeyEvent.KEYCODE_MENU) {
137 | Log.d(TAG, "KEYCODE_MENU");
138 | }
139 | }
140 | };
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/home_apps_focused.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/drawable-hdpi/home_apps_focused.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/home_apps_unfocused.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selector_home_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_stag_grid.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_grid_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 | 1dp
7 | 2dp
8 | 3dp
9 | 4dp
10 | 5dp
11 | 8dp
12 | 10dp
13 | 12dp
14 | 15dp
15 | 20dp
16 | 25dp
17 | 30dp
18 | 35dp
19 | 40dp
20 | 45dp
21 | 50dp
22 | 60dp
23 | 80dp
24 | 100dp
25 | 130dp
26 |
27 | 12sp
28 | 13sp
29 | 14sp
30 | 15sp
31 | 16sp
32 | 18sp
33 | 20sp
34 | 22sp
35 | 24sp
36 | 26sp
37 | 28sp
38 | 30sp
39 | 35sp
40 | 40sp
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ScrollRecyclerView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/art/ScrollRecyclerView_art.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/art/ScrollRecyclerView_art.gif
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 | maven { url 'https://jitpack.io' }
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:4.0.1'
11 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lishide/ScrollRecyclerView/5a0153d2ec92c1932c96542e9fe0cd5ce6b7e59b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Aug 08 10:01:14 CST 2020
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-6.1.1-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 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 |
4 | android {
5 | compileSdkVersion 29
6 | buildToolsVersion "29.0.3"
7 |
8 | defaultConfig {
9 | minSdkVersion 15
10 | targetSdkVersion 29
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
15 |
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | api fileTree(dir: 'libs', include: ['*.jar'])
27 | api 'androidx.appcompat:appcompat:1.2.0'
28 | api 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
29 | }
30 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/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 C:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/java/com/lishide/recyclerview/scroll/ScrollRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.lishide.recyclerview.scroll;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.util.Log;
6 | import android.view.View;
7 | import android.widget.Scroller;
8 |
9 | import androidx.recyclerview.widget.LinearLayoutManager;
10 | import androidx.recyclerview.widget.RecyclerView;
11 | import androidx.recyclerview.widget.StaggeredGridLayoutManager;
12 |
13 | /**
14 | * ScrollRecyclerView——RecyclerView 横向 / 纵向滚动网格布局。
15 | * 适用于 Android 平板、Android TV 或其他定制化 Android 设备等,
16 | * 使用遥控器方向导航键控制列表滑动及 item 选择状态。
17 | *
18 | * @author lishide
19 | * @date 2017/4/12
20 | */
21 | public class ScrollRecyclerView extends RecyclerView {
22 | private static final String TAG = ScrollRecyclerView.class.getSimpleName();
23 | /**
24 | * 一个滚动对象
25 | */
26 | private Scroller mScroller;
27 | private int mLastX = 0;
28 | private int specialLeft, specialRight;
29 | private int childWidth;
30 |
31 | public ScrollRecyclerView(Context context) {
32 | super(context);
33 | init(context);
34 | }
35 |
36 | public ScrollRecyclerView(Context context, AttributeSet attrs) {
37 | super(context, attrs);
38 | init(context);
39 | }
40 |
41 | public ScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
42 | super(context, attrs, defStyle);
43 | init(context);
44 | }
45 |
46 | /**
47 | * 一个初始化方法,传入了一个上下文对象,用来初始化滚动对象
48 | *
49 | * @param context 上下文
50 | */
51 | private void init(Context context) {
52 | mScroller = new Scroller(context);
53 | }
54 |
55 | /**
56 | * 重写计算滚动方法
57 | */
58 | @Override
59 | public void computeScroll() {
60 | if (mScroller != null && mScroller.computeScrollOffset()) {
61 | // Log.i("computeScroll", "curX:" + mScroller.getCurrX());
62 | scrollBy(mLastX - mScroller.getCurrX(), 0);
63 | mLastX = mScroller.getCurrX();
64 | postInvalidate();
65 | }
66 | }
67 |
68 | /**
69 | * 滚动到目标位置
70 | * 其中 (fx, fy) 表示最终要滚到的目标位置的坐标值,duration 表示期间滚动的耗时。
71 | *
72 | * @param fx 目标位置的X向坐标值
73 | * @param fy 目标位置的Y向坐标值
74 | * @param duration 滚动到目标位置所消耗的时间毫秒值
75 | */
76 | @SuppressWarnings("unused")
77 | public void smoothScrollTo(int fx, int fy, int duration) {
78 | int dx = 0;
79 | int dy = 0;
80 | // 计算变化的位移量
81 | if (fx != 0) {
82 | dx = fx - mScroller.getFinalX();
83 | }
84 | if (fy != 0) {
85 | dy = fy - mScroller.getFinalY();
86 | }
87 | Log.i(TAG, "fx:" + fx + ", getFinalX:" + mScroller.getFinalX() + ", dx:" + dx);
88 | smoothScrollBy(dx, dy, duration);
89 | }
90 |
91 | /**
92 | * 设置滚动的相对偏移
93 | */
94 | public void smoothScrollBy(int dx, int dy, int duration) {
95 | if (duration > 0) {
96 | mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, duration);
97 | } else {
98 | // 设置mScroller的滚动偏移量
99 | mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
100 | }
101 | // 重绘整个view,重绘过程会调用到computeScroll()方法。
102 | // 这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
103 | invalidate();
104 | }
105 |
106 | /**
107 | * 检查自动调节
108 | *
109 | * @param position 要检查的位置
110 | */
111 | @SuppressWarnings("unused")
112 | public void checkAutoAdjust(int position) {
113 | int childCount = getChildCount();
114 | // 获取可视范围内的选项的头尾位置
115 | int firstVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
116 | int lastVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
117 | Log.d(TAG, "childCount:" + childCount + ", position:" + position
118 | + ", firstVisibleItemPosition:" + firstVisibleItemPosition
119 | + " lastVisibleItemPosition:" + lastVisibleItemPosition);
120 | if (position == (firstVisibleItemPosition + 1) || position == firstVisibleItemPosition) {
121 | leftScrollBy(position, firstVisibleItemPosition);
122 | } else if (position == (lastVisibleItemPosition - 1) || position == lastVisibleItemPosition) {
123 | rightScrollBy(position, lastVisibleItemPosition);
124 | }
125 | }
126 |
127 | /**
128 | * 当前位置需要向右平移
129 | *
130 | * @param position pos
131 | * @param firstVisibleItemPosition 可见的第一个Item的pos
132 | */
133 | private void leftScrollBy(int position, int firstVisibleItemPosition) {
134 | View leftChild = getChildAt(0);
135 | if (leftChild != null) {
136 | int startLeft = leftChild.getLeft();
137 | int endLeft = (position == firstVisibleItemPosition ? leftChild.getWidth() : 0);
138 | Log.d(TAG, "startLeft:" + startLeft + " endLeft" + endLeft);
139 | autoAdjustScroll(startLeft, endLeft);
140 | }
141 | }
142 |
143 | /**
144 | * 当前位置需要向左平移
145 | *
146 | * @param position pos
147 | * @param lastVisibleItemPosition 可见的最后一个Item的pos
148 | */
149 | private void rightScrollBy(int position, int lastVisibleItemPosition) {
150 | int childCount = getChildCount();
151 | View rightChild = getChildAt(childCount - 1);
152 | if (rightChild != null) {
153 | int startRight = rightChild.getRight() - getWidth();
154 | int endRight = (position == lastVisibleItemPosition ? (-1 * rightChild.getWidth()) : 0);
155 | Log.d(TAG, "startRight:" + startRight + " endRight:" + endRight);
156 | autoAdjustScroll(startRight, endRight);
157 | }
158 | }
159 |
160 | /**
161 | * 自动调节滑动
162 | *
163 | * @param start 滑动起始位置
164 | * @param end 滑动结束位置
165 | */
166 | private void autoAdjustScroll(int start, int end) {
167 | mLastX = start;
168 | mScroller.startScroll(start, 0, end - start, 0);
169 | postInvalidate();
170 | }
171 |
172 | /**
173 | * 将指定 item 平滑移动到整个 view 的中间位置
174 | *
175 | * @param position 指定的 item 的位置
176 | */
177 | public void smoothHorizontalScrollToNext(int position) {
178 | Log.d(TAG, "position=" + position);
179 | StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) getLayoutManager();
180 | int[] startItems = manager.findFirstVisibleItemPositions(null);
181 | int[] endItems = manager.findLastVisibleItemPositions(null);
182 | if (position == 0) {
183 | int parentWidth = getWidth();
184 | View firstView = getChildAt(0);
185 | mLastX = 1174;
186 | mScroller.startScroll(mLastX, 0, -500, 0);
187 | postInvalidate();
188 |
189 | childWidth = firstView.getWidth();
190 | int preLastPos = endItems[0] - 1;
191 | View specialView = getChildAt(preLastPos);
192 | if (specialView == null) {
193 | return;
194 | }
195 | specialLeft = specialView.getLeft();
196 | specialRight = parentWidth - specialLeft;
197 | Log.d(TAG, "一屏的倒数第二行位置是:" + preLastPos + ", 父容器宽度:" + parentWidth
198 | + ", 超出位置(极右位置):" + specialLeft + ", 不达位置(极左位置):" + specialRight);
199 | }
200 |
201 | int targetPos = position - startItems[0];
202 | View targetView = getChildAt(targetPos);
203 | if (targetView == null) {
204 | Log.i(TAG, "TargetView is null!");
205 | return;
206 | }
207 | int targetLeft = targetView.getLeft();
208 | int targetRight = targetView.getRight();
209 | Log.d(TAG, "目标位置:" + targetPos + ", 目标左位置:" + targetLeft + ", 目标右位置:" + targetRight);
210 |
211 | if (targetLeft > specialLeft) {
212 | // 获得焦点的不全显示 item 将自动全显示。
213 | // 因此,到达极右位置只需移动下一个不全显示的偏置距离
214 | mLastX = targetLeft;
215 | mScroller.startScroll(targetLeft, 0, -childWidth / 2, 0);
216 | postInvalidate();
217 | Log.d(TAG, "<----");
218 | } else if (targetRight < specialRight) {
219 | // 到达极左位置
220 | mLastX = targetRight;
221 | mScroller.startScroll(targetRight, 0, childWidth / 2, 0);
222 | postInvalidate();
223 | Log.d(TAG, "---->");
224 | }
225 | }
226 |
227 | }
228 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/java/com/lishide/recyclerview/scroll/SpaceItemDecoration.java:
--------------------------------------------------------------------------------
1 | package com.lishide.recyclerview.scroll;
2 |
3 | import android.graphics.Rect;
4 | import android.view.View;
5 |
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | public class SpaceItemDecoration extends RecyclerView.ItemDecoration {
9 | private int mRight;
10 | private int mBottom;
11 |
12 | public SpaceItemDecoration(int right, int bottom) {
13 | this.mRight = right;
14 | this.mBottom = bottom;
15 | }
16 |
17 | @Override
18 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
19 | outRect.right = mRight;
20 | outRect.bottom = mBottom;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/java/com/lishide/recyclerview/scroll/listener/OnItemClickListener.java:
--------------------------------------------------------------------------------
1 | package com.lishide.recyclerview.scroll.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * item 点击监听
7 | *
8 | * @author lishide
9 | * @date 2017/4/12
10 | */
11 | public interface OnItemClickListener {
12 | /**
13 | * item 点击
14 | *
15 | * @param view view
16 | * @param position position
17 | */
18 | void onItemClick(View view, int position);
19 | }
20 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/java/com/lishide/recyclerview/scroll/listener/OnItemKeyListener.java:
--------------------------------------------------------------------------------
1 | package com.lishide.recyclerview.scroll.listener;
2 |
3 | import android.view.KeyEvent;
4 | import android.view.View;
5 |
6 | /**
7 | * 遥控器其他按键监听
8 | *
9 | * @author lishide
10 | * @date 2017/4/12
11 | */
12 | public interface OnItemKeyListener {
13 | /**
14 | * 遥控器其他按键
15 | *
16 | * @param view view
17 | * @param keyCode keyCode
18 | * @param event event
19 | * @param position position
20 | */
21 | void onItemKey(View view, int keyCode, KeyEvent event, int position);
22 | }
23 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/java/com/lishide/recyclerview/scroll/listener/OnItemLongClickListener.java:
--------------------------------------------------------------------------------
1 | package com.lishide.recyclerview.scroll.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * item 长按监听
7 | *
8 | * @author lishide
9 | * @date 2017/4/12
10 | */
11 | public interface OnItemLongClickListener {
12 | /**
13 | * item 长按
14 | *
15 | * @param view view
16 | * @param position position
17 | */
18 | void onItemLongClick(View view, int position);
19 | }
20 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/java/com/lishide/recyclerview/scroll/listener/OnItemSelectedListener.java:
--------------------------------------------------------------------------------
1 | package com.lishide.recyclerview.scroll.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * item 选定监听
7 | *
8 | * @author lishide
9 | * @date 2017/4/12
10 | */
11 | public interface OnItemSelectedListener {
12 | /**
13 | * item 选定
14 | *
15 | * @param view view
16 | * @param position position
17 | */
18 | void onItemSelected(View view, int position);
19 | }
20 |
--------------------------------------------------------------------------------
/recyclerview-scroll-lib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RecyclerView-Scroll-lib
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':recyclerview-scroll-lib'
2 |
--------------------------------------------------------------------------------