├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ccb │ │ └── arcselect │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ccb │ │ │ └── arcselect │ │ │ ├── StartActivity.java │ │ │ ├── ui │ │ │ ├── ArcActivity.java │ │ │ ├── ArcSelectActivity.java │ │ │ ├── ArcSelectRotateActivity.java │ │ │ ├── AutoSelectActivity.java │ │ │ ├── BottomArcActivity.java │ │ │ ├── CircleArcActivity.java │ │ │ ├── HorizontalSelectActivity.java │ │ │ └── PadingArcActivity.java │ │ │ ├── utils │ │ │ ├── CenterItemUtils.java │ │ │ ├── TUtils.java │ │ │ └── UiUtils.java │ │ │ └── view │ │ │ ├── CircleMatrixView.java │ │ │ ├── CircleRelativeLayout.java │ │ │ ├── HMatrixTranslateLayout.java │ │ │ └── MatrixTranslateLayout.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── ic_location.webp │ │ └── text_background.xml │ │ ├── layout │ │ ├── activity_arc_select_r.xml │ │ ├── activity_auto_select.xml │ │ ├── activity_auto_select_h.xml │ │ ├── activity_bottom_arc.xml │ │ ├── activity_rv.xml │ │ ├── activity_select_arc.xml │ │ ├── activity_start.xml │ │ ├── item_arc.xml │ │ ├── item_auto_select.xml │ │ ├── item_auto_select_h.xml │ │ ├── item_bottom_arc.xml │ │ ├── item_circle.xml │ │ └── item_pading.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.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 │ │ ├── ebooks_disk_background.png │ │ ├── ebooks_disk_check.png │ │ ├── ebooks_disk_scale.png │ │ ├── ebooks_disk_scale_white.png │ │ ├── ebooks_half_disc.png │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attr.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ccb │ └── arcselect │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── arclist.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArcSelectList 2 | 弧形列表、滑动后自动选中 , 使用RecyclerView实现; 横向选择器、竖向选择器 、 圆弧形选择器; 3 | > 下载地址:http://d.7short.com/ArcSelectList 4 | - [Android 弧形列表转盘的实现(一),弧形列表](https://blog.csdn.net/qq_35605213/article/details/106194839) 5 | - [Android 弧形列表转盘的实现(二),列表自动选中;RecyclerView滑动后自动选中居中的条目,RecyclerView实现WheelView效果](https://blog.csdn.net/qq_35605213/article/details/106209700) 6 | - [Android 弧形列表转盘的实现(三),View跟随RecyclerView做旋转动画;](https://blog.csdn.net/qq_35605213/article/details/106213525) 7 | - [Android 横向列表滑动自动选中,RecyclerView实现;](https://cuichen.blog.csdn.net/article/details/106476783) 8 | 9 | ![示例图片](https://github.com/CuiChenbo/ArcSelectList/blob/master/images/arclist.gif) 10 | -------------------------------------------------------------------------------- /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.ccb.arcselectlist" 8 | minSdkVersion 17 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-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'androidx.appcompat:appcompat:1.0.2' 25 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 28 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 29 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ccb/arcselect/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.example.yuanview", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/StartActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | 9 | import com.ccb.arcselect.ui.ArcActivity; 10 | import com.ccb.arcselect.ui.ArcSelectRotateActivity; 11 | import com.ccb.arcselect.ui.AutoSelectActivity; 12 | import com.ccb.arcselect.ui.BottomArcActivity; 13 | import com.ccb.arcselect.ui.CircleArcActivity; 14 | import com.ccb.arcselect.ui.HorizontalSelectActivity; 15 | import com.ccb.arcselect.ui.PadingArcActivity; 16 | import com.ccb.arcselect.ui.ArcSelectActivity; 17 | 18 | public class StartActivity extends AppCompatActivity implements View.OnClickListener { 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_start); 24 | findViewById(R.id.bt_arc_select_r).setOnClickListener(this); 25 | findViewById(R.id.bt_arc).setOnClickListener(this); 26 | findViewById(R.id.bt_arc2).setOnClickListener(this); 27 | findViewById(R.id.bt_auto_select).setOnClickListener(this); 28 | findViewById(R.id.bt_carc).setOnClickListener(this); 29 | findViewById(R.id.bt_arc_select_).setOnClickListener(this); 30 | findViewById(R.id.bt_auto_select_h).setOnClickListener(this); 31 | findViewById(R.id.bt_auto_select_h2).setOnClickListener(this); 32 | } 33 | 34 | @Override 35 | public void onClick(View view) { 36 | switch (view.getId()) { 37 | case R.id.bt_arc_select_r: 38 | start(ArcSelectRotateActivity.class); 39 | break; 40 | case R.id.bt_arc: 41 | start(ArcActivity.class); 42 | break; 43 | case R.id.bt_arc2: 44 | start(PadingArcActivity.class); 45 | break; 46 | case R.id.bt_auto_select: 47 | start(AutoSelectActivity.class); 48 | break; 49 | case R.id.bt_carc: 50 | start(CircleArcActivity.class); 51 | break; 52 | case R.id.bt_arc_select_: 53 | start(ArcSelectActivity.class); 54 | break; 55 | case R.id.bt_auto_select_h: 56 | start(HorizontalSelectActivity.class); 57 | break; 58 | case R.id.bt_auto_select_h2: 59 | start(BottomArcActivity.class); 60 | break; 61 | } 62 | } 63 | 64 | private void start(Class clazz) { 65 | startActivity(new Intent(this, clazz)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/ArcActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.ViewTreeObserver; 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.recyclerview.widget.LinearLayoutManager; 14 | import androidx.recyclerview.widget.RecyclerView; 15 | 16 | import com.ccb.arcselect.R; 17 | import com.ccb.arcselect.utils.TUtils; 18 | import com.ccb.arcselect.view.MatrixTranslateLayout; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * 矩阵实现弧形列表 RecyclerView 25 | */ 26 | public class ArcActivity extends AppCompatActivity { 27 | 28 | 29 | private RecyclerView recyclerView; 30 | private MAdapter mAdapter; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_rv); 36 | recyclerView = findViewById(R.id.rv); 37 | initData(); 38 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 39 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 40 | @Override 41 | public void onGlobalLayout() { 42 | findView(); 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 44 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 45 | } 46 | } 47 | }); 48 | 49 | 50 | } 51 | 52 | private List mDatas; 53 | private void initData() { 54 | if (mDatas == null) mDatas = new ArrayList<>(); 55 | for (int i = 0; i < 99; i++) { 56 | mDatas.add("CAR_Item"+i); 57 | } 58 | 59 | } 60 | 61 | private void findView() { 62 | mAdapter = new MAdapter(); 63 | recyclerView.setAdapter(mAdapter); 64 | 65 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 66 | @Override 67 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 68 | super.onScrollStateChanged(recyclerView, newState); 69 | } 70 | 71 | @Override 72 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 73 | super.onScrolled(recyclerView, dx, dy); 74 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 75 | recyclerView.getChildAt(i).invalidate(); 76 | } 77 | } 78 | }); 79 | 80 | } 81 | 82 | 83 | class MAdapter extends RecyclerView.Adapter { 84 | @NonNull 85 | @Override 86 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 87 | return new MAdapter.VH(LayoutInflater.from(ArcActivity.this).inflate(R.layout.item_arc, parent, false)); 88 | } 89 | 90 | @Override 91 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 92 | MAdapter.VH vh = (MAdapter.VH) holder; 93 | ((MatrixTranslateLayout) vh.itemView).setParentHeight(recyclerView.getHeight()); 94 | vh.tv.setText(mDatas.get(position)); 95 | final int fp = position; 96 | vh.itemView.setOnClickListener(new View.OnClickListener() { 97 | @Override 98 | public void onClick(View v) { 99 | TUtils.show(ArcActivity.this, "点击" +mDatas.get(fp)); 100 | } 101 | }); 102 | } 103 | 104 | @Override 105 | public int getItemCount() { 106 | return mDatas.size(); 107 | } 108 | 109 | class VH extends RecyclerView.ViewHolder { 110 | 111 | public TextView tv; 112 | 113 | public VH(@NonNull View itemView) { 114 | super(itemView); 115 | tv = itemView.findViewById(R.id.tv); 116 | } 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/ArcSelectActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.text.TextUtils; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.ViewTreeObserver; 17 | import android.view.animation.DecelerateInterpolator; 18 | import android.widget.AbsListView; 19 | import android.widget.ImageView; 20 | import android.widget.TextView; 21 | 22 | import com.ccb.arcselect.R; 23 | import com.ccb.arcselect.utils.CenterItemUtils; 24 | import com.ccb.arcselect.utils.TUtils; 25 | import com.ccb.arcselect.utils.UiUtils; 26 | import com.ccb.arcselect.view.MatrixTranslateLayout; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | /** 32 | * 矩阵实现弧形列表 && 滑动后自动选中居中的条目 33 | * 未使用精确计算居中 34 | */ 35 | public class ArcSelectActivity extends AppCompatActivity { 36 | 37 | 38 | private ImageView iv; 39 | private RecyclerView recyclerView; 40 | private MAdapter mAdapter; 41 | private int centerToTopDistance; //RecyclerView高度的一半 ,也就是控件中间位置到顶部的距离 , 42 | private int childViewHalfCount = 0; //当前RecyclerView一半最多可以存在几个Item 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_select_arc); 48 | recyclerView = findViewById(R.id.rv); 49 | iv = findViewById(R.id.iv); 50 | init(); 51 | 52 | } 53 | 54 | private void init() { 55 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 56 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 57 | @Override 58 | public void onGlobalLayout() { 59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 60 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 61 | } 62 | 63 | centerToTopDistance = recyclerView.getHeight() / 2; 64 | 65 | int childViewHeight = UiUtils.dip2px(ArcSelectActivity.this, 68); //68是当前已知的 Item的高度 66 | childViewHalfCount = (recyclerView.getHeight() / childViewHeight + 1) / 2; 67 | initData(); 68 | findView(); 69 | 70 | } 71 | }); 72 | } 73 | 74 | private List mDatas; 75 | 76 | private void initData() { 77 | if (mDatas == null) mDatas = new ArrayList<>(); 78 | for (int i = 0; i < 55; i++) { 79 | mDatas.add("CAR_Item" + i); 80 | } 81 | for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 82 | mDatas.add(0, null); 83 | } 84 | for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 85 | mDatas.add(null); 86 | } 87 | 88 | 89 | } 90 | 91 | private boolean isTouch = false; //用户主动触摸后的标记 92 | private List centerViewItems = new ArrayList<>(); 93 | 94 | private void findView() { 95 | mAdapter = new MAdapter(); 96 | recyclerView.setAdapter(mAdapter); 97 | 98 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 99 | @Override 100 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 101 | super.onScrollStateChanged(recyclerView, newState); 102 | if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { 103 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 104 | int fi = linearLayoutManager.findFirstVisibleItemPosition(); 105 | int la = linearLayoutManager.findLastVisibleItemPosition(); 106 | Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la +" 是否需要移动:"+isTouch); 107 | if (isTouch) { 108 | isTouch = false; 109 | //粗略获取最中间的Item View 110 | int centerPositionDiffer = (la - fi) / 2; 111 | int centerChildViewPosition = fi + centerPositionDiffer; 112 | 113 | centerViewItems.clear(); 114 | //遍历循环,获取到和中线相差最小的条目索引(精准查找最居中的条目) 115 | if (centerChildViewPosition != 0){ 116 | for (int i = centerChildViewPosition -1 ; i < centerChildViewPosition+2; i++) { 117 | View cView = recyclerView.getLayoutManager().findViewByPosition(i); 118 | int viewTop = cView.getTop()+(cView.getHeight()/2); 119 | centerViewItems.add(new CenterItemUtils.CenterViewItem(i ,Math.abs(centerToTopDistance - viewTop))); 120 | } 121 | 122 | CenterItemUtils.CenterViewItem centerViewItem = CenterItemUtils.getMinDifferItem(centerViewItems); 123 | centerChildViewPosition = centerViewItem.position; 124 | } 125 | scrollToCenter(centerChildViewPosition); 126 | } 127 | } 128 | } 129 | 130 | @Override 131 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 132 | super.onScrolled(recyclerView, dx, dy); 133 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 134 | recyclerView.getChildAt(i).invalidate(); 135 | } 136 | } 137 | }); 138 | 139 | recyclerView.setOnTouchListener(new View.OnTouchListener() { 140 | @Override 141 | public boolean onTouch(View view, MotionEvent motionEvent) { 142 | isTouch = true; 143 | return false; 144 | } 145 | }); 146 | recyclerView.postDelayed(new Runnable() { 147 | @Override 148 | public void run() { 149 | scrollToCenter(childViewHalfCount); 150 | } 151 | }, 100L); 152 | 153 | } 154 | 155 | private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); 156 | /** 157 | * 移动指定索引到中心处 , 只可以移动可见区域的内容 158 | * @param position 159 | */ 160 | private void scrollToCenter(int position){ 161 | position = position < childViewHalfCount ? childViewHalfCount : position; 162 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 163 | 164 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 165 | View childView = linearLayoutManager.findViewByPosition(position); 166 | Log.i("ccb", "滑动后中间View的索引: " + position); 167 | //把当前View移动到居中位置 168 | if (childView == null) return; 169 | int childVhalf = childView.getHeight() / 2; 170 | int childViewTop = childView.getTop(); 171 | int viewCTop = centerToTopDistance; 172 | int smoothDistance = childViewTop - viewCTop + childVhalf; 173 | Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop 174 | + "\n当前居中控件距离顶部距离: " + childViewTop 175 | + "\n当前居中控件的一半高度: " + childVhalf 176 | + "\n滑动后再次移动距离: " + smoothDistance); 177 | recyclerView.smoothScrollBy(0, smoothDistance,decelerateInterpolator); 178 | mAdapter.setSelectPosition(position); 179 | TUtils.show(ArcSelectActivity.this , "滑动后选中:" + mDatas.get(position)); 180 | } 181 | 182 | /** 183 | * 移动指定索引 184 | * @param position 185 | */ 186 | private void smoothToPosition(int position){ 187 | position = position < childViewHalfCount ? childViewHalfCount : position; 188 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 189 | 190 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 191 | linearLayoutManager.scrollToPosition(position); 192 | } 193 | 194 | 195 | class MAdapter extends RecyclerView.Adapter { 196 | @NonNull 197 | @Override 198 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 199 | return new VH(LayoutInflater.from(ArcSelectActivity.this).inflate(R.layout.item_arc, parent, false)); 200 | } 201 | 202 | @Override 203 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 204 | VH vh = (VH) holder; 205 | ((MatrixTranslateLayout) vh.itemView).setParentHeight(recyclerView.getHeight()); 206 | 207 | if (selectPosition == position) { 208 | vh.tv.setTextColor(getResources().getColor(R.color.textSelect)); 209 | } else { 210 | vh.tv.setTextColor(getResources().getColor(R.color.white)); 211 | } 212 | if (TextUtils.isEmpty(mDatas.get(position))){ 213 | vh.itemView.setVisibility(View.INVISIBLE); 214 | }else { 215 | vh.itemView.setVisibility(View.VISIBLE); 216 | vh.tv.setText(mDatas.get(position)); 217 | } 218 | final int fp = position; 219 | vh.itemView.setOnClickListener(new View.OnClickListener() { 220 | @Override 221 | public void onClick(View v) { 222 | TUtils.show(ArcSelectActivity.this , "点击" + mDatas.get(fp)); 223 | scrollToCenter(fp); 224 | } 225 | }); 226 | } 227 | 228 | private int selectPosition = -1; 229 | 230 | public void setSelectPosition(int cposition) { 231 | selectPosition = cposition; 232 | // notifyItemChanged(cposition); 233 | notifyDataSetChanged(); 234 | } 235 | 236 | @Override 237 | public int getItemCount() { 238 | return mDatas.size(); 239 | } 240 | 241 | class VH extends RecyclerView.ViewHolder { 242 | 243 | public TextView tv; 244 | 245 | public VH(@NonNull View itemView) { 246 | super(itemView); 247 | tv = itemView.findViewById(R.id.tv); 248 | } 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/ArcSelectRotateActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | import android.animation.ObjectAnimator; 9 | import android.os.Build; 10 | import android.os.Bundle; 11 | import android.text.TextUtils; 12 | import android.util.Log; 13 | import android.view.LayoutInflater; 14 | import android.view.MotionEvent; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.ViewTreeObserver; 18 | import android.view.animation.DecelerateInterpolator; 19 | import android.widget.AbsListView; 20 | import android.widget.ImageView; 21 | import android.widget.RelativeLayout; 22 | import android.widget.TextView; 23 | 24 | import com.ccb.arcselect.R; 25 | import com.ccb.arcselect.utils.TUtils; 26 | import com.ccb.arcselect.utils.UiUtils; 27 | import com.ccb.arcselect.view.MatrixTranslateLayout; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | 33 | /** 34 | * 矩阵实现弧形列表 && 滑动后自动选中居中的条目 && 图片跟随旋转 35 | * 未使用精确计算居中条目 , 可以参考CenterItemUtils类 , 或其它选中居中的界面的实现 36 | */ 37 | public class ArcSelectRotateActivity extends AppCompatActivity { 38 | 39 | private RelativeLayout rvDish; 40 | private ImageView ivScale; 41 | private RecyclerView recyclerView; 42 | private MAdapter mAdapter; 43 | private int centerToTopDistance; //RecyclerView高度的一半 ,也就是控件中间位置到顶部的距离 , 44 | private int childViewHalfCount = 0; //当前RecyclerView一半最多可以存在几个Item 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_arc_select_r); 50 | recyclerView = findViewById(R.id.rv); 51 | rvDish = findViewById(R.id.rvDish); 52 | ivScale = findViewById(R.id.ivScale); 53 | setTranslationX(rvDish , UiUtils.dip2px(this , 200)*-1); 54 | init(); 55 | 56 | } 57 | 58 | private void init() { 59 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 60 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 61 | @Override 62 | public void onGlobalLayout() { 63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 64 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 65 | } 66 | 67 | centerToTopDistance = recyclerView.getHeight() / 2; 68 | 69 | int childViewHeight = UiUtils.dip2px(ArcSelectRotateActivity.this, 68); //68是当前已知的 Item的高度 70 | childViewHalfCount = (recyclerView.getHeight() / childViewHeight + 1) / 2; 71 | initData(); 72 | findView(); 73 | 74 | } 75 | }); 76 | } 77 | 78 | private List mDatas; 79 | 80 | private void initData() { 81 | if (mDatas == null) mDatas = new ArrayList<>(); 82 | for (int i = 0; i < 55; i++) { 83 | mDatas.add("CAR_Item" + i); 84 | } 85 | for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 86 | mDatas.add(0, null); 87 | } 88 | for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 89 | mDatas.add(null); 90 | } 91 | 92 | 93 | } 94 | 95 | private boolean isTouch = false; //用户主动触摸后的标记 96 | 97 | private void findView() { 98 | mAdapter = new MAdapter(); 99 | recyclerView.setAdapter(mAdapter); 100 | 101 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 102 | @Override 103 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 104 | super.onScrollStateChanged(recyclerView, newState); 105 | if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { 106 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 107 | int fi = linearLayoutManager.findFirstVisibleItemPosition(); 108 | int la = linearLayoutManager.findLastVisibleItemPosition(); 109 | Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la +" 是否需要移动:"+isTouch); 110 | if (isTouch) { 111 | isTouch = false; 112 | //粗略获取最中间的Item View 113 | int centerPositionDiffer = (la - fi) / 2; 114 | int centerChildViewPosition = fi + centerPositionDiffer; 115 | 116 | centerChildViewPosition = centerChildViewPosition < childViewHalfCount ? childViewHalfCount : centerChildViewPosition; 117 | centerChildViewPosition = centerChildViewPosition < mAdapter.getItemCount() - childViewHalfCount -1 ? centerChildViewPosition : mAdapter.getItemCount() - childViewHalfCount -1; 118 | scrollToCenter(centerChildViewPosition); 119 | // View childView = recyclerView.getLayoutManager().findViewByPosition(centerChildViewPosition); 120 | // Log.i("ccb", "滑动后中间View的索引: " + centerChildViewPosition); 121 | // //把当前View移动到居中位置 122 | // if (childView == null) return; 123 | // int childVhalf = childView.getHeight() / 2; 124 | // int childViewTop = childView.getTop(); 125 | // int viewCTop = centerToTopDistance; 126 | // int smoothDistance = childViewTop - viewCTop + childVhalf; 127 | // Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop 128 | // + "\n当前居中控件距离顶部距离: " + childViewTop 129 | // + "\n当前居中控件的一半高度: " + childVhalf 130 | // + "\n滑动后再次移动距离: " + smoothDistance); 131 | // recyclerView.smoothScrollBy(0, smoothDistance); 132 | // mAdapter.setSelectPosition(centerChildViewPosition); 133 | // TUtils.show(ArcSelectRotateActivity.this , "滑动后选中:" + mDatas.get(centerChildViewPosition)); 134 | } 135 | } 136 | } 137 | 138 | @Override 139 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 140 | super.onScrolled(recyclerView, dx, dy); 141 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 142 | recyclerView.getChildAt(i).invalidate(); 143 | } 144 | Log.i("ccb", "onScrolled: dx"+dx +" ,dy"+dy); 145 | float dyy = dy*-1; 146 | setRotate(ivScale,dyy/4); 147 | } 148 | }); 149 | 150 | recyclerView.setOnTouchListener(new View.OnTouchListener() { 151 | @Override 152 | public boolean onTouch(View view, MotionEvent motionEvent) { 153 | isTouch = true; 154 | return false; 155 | } 156 | }); 157 | recyclerView.postDelayed(new Runnable() { 158 | @Override 159 | public void run() { 160 | scrollToCenter(childViewHalfCount); 161 | } 162 | }, 100L); 163 | 164 | } 165 | 166 | 167 | private float values = 0f; 168 | private float newValues = 0f; 169 | private ObjectAnimator rotationAnimator; 170 | public void setRotate(View view , float dy) { 171 | if (rotationAnimator == null) 172 | rotationAnimator = ObjectAnimator.ofFloat(view,"rotation",0f,10f); 173 | newValues = values + dy; 174 | rotationAnimator.setDuration(10L); 175 | rotationAnimator.setFloatValues(values,newValues); 176 | rotationAnimator.start(); 177 | values = newValues; 178 | } 179 | 180 | public void setTranslationX(View view , float dy) { 181 | ObjectAnimator translationYAnimator = ObjectAnimator.ofFloat(view,"translationX",0f,dy); 182 | translationYAnimator.setDuration(1L); 183 | translationYAnimator.start(); 184 | } 185 | 186 | private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); 187 | /** 188 | * 移动指定索引到中心处 , 只可以移动可见区域的内容 189 | * @param position 190 | */ 191 | private void scrollToCenter(int position){ 192 | position = position < childViewHalfCount ? childViewHalfCount : position; 193 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 194 | 195 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 196 | View childView = linearLayoutManager.findViewByPosition(position); 197 | Log.i("ccb", "滑动后中间View的索引: " + position); 198 | //把当前View移动到居中位置 199 | if (childView == null) return; 200 | int childVhalf = childView.getHeight() / 2; 201 | int childViewTop = childView.getTop(); 202 | int viewCTop = centerToTopDistance; 203 | int smoothDistance = childViewTop - viewCTop + childVhalf; 204 | Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop 205 | + "\n当前居中控件距离顶部距离: " + childViewTop 206 | + "\n当前居中控件的一半高度: " + childVhalf 207 | + "\n滑动后再次移动距离: " + smoothDistance); 208 | recyclerView.smoothScrollBy(0, smoothDistance,decelerateInterpolator); 209 | mAdapter.setSelectPosition(position); 210 | TUtils.show(ArcSelectRotateActivity.this , "滑动后选中:" + mDatas.get(position)); 211 | } 212 | 213 | /** 214 | * 移动指定索引 215 | * @param position 216 | */ 217 | private void smoothToPosition(int position){ 218 | position = position < childViewHalfCount ? childViewHalfCount : position; 219 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 220 | 221 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 222 | linearLayoutManager.scrollToPosition(position); 223 | } 224 | 225 | 226 | class MAdapter extends RecyclerView.Adapter { 227 | @NonNull 228 | @Override 229 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 230 | return new VH(LayoutInflater.from(ArcSelectRotateActivity.this).inflate(R.layout.item_arc, parent, false)); 231 | } 232 | 233 | @Override 234 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 235 | VH vh = (VH) holder; 236 | ((MatrixTranslateLayout) vh.itemView).setParentHeight(recyclerView.getHeight()); 237 | 238 | if (selectPosition == position) { 239 | vh.tv.setTextColor(getResources().getColor(R.color.textSelect)); 240 | } else { 241 | vh.tv.setTextColor(getResources().getColor(R.color.white)); 242 | } 243 | if (TextUtils.isEmpty(mDatas.get(position))){ 244 | vh.itemView.setVisibility(View.INVISIBLE); 245 | }else { 246 | vh.itemView.setVisibility(View.VISIBLE); 247 | vh.tv.setText(mDatas.get(position)); 248 | } 249 | final int fp = position; 250 | vh.itemView.setOnClickListener(new View.OnClickListener() { 251 | @Override 252 | public void onClick(View v) { 253 | TUtils.show(ArcSelectRotateActivity.this , "点击" + mDatas.get(fp)); 254 | scrollToCenter(fp); 255 | } 256 | }); 257 | } 258 | 259 | private int selectPosition = -1; 260 | 261 | public void setSelectPosition(int cposition) { 262 | selectPosition = cposition; 263 | // notifyItemChanged(cposition); 264 | notifyDataSetChanged(); 265 | } 266 | 267 | @Override 268 | public int getItemCount() { 269 | return mDatas.size(); 270 | } 271 | 272 | class VH extends RecyclerView.ViewHolder { 273 | 274 | public TextView tv; 275 | 276 | public VH(@NonNull View itemView) { 277 | super(itemView); 278 | tv = itemView.findViewById(R.id.tv); 279 | } 280 | } 281 | } 282 | 283 | 284 | // @Deprecated 285 | // public void changeGroupFlag(Object obj) throws Exception { 286 | // Field[] f = obj.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredFields(); // 获得成员映射数组 287 | // for (Field tem : f) { 288 | // if (tem.getName().equals("mGroupFlags")) { 289 | // tem.setAccessible(true); 290 | // Integer mGroupFlags = (Integer) tem.get(obj); 291 | // int newGroupFlags = mGroupFlags & 0xfffff8; 292 | // tem.set(obj, newGroupFlags); 293 | // } 294 | // } 295 | // } 296 | } 297 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/AutoSelectActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.ViewTreeObserver; 16 | import android.view.animation.DecelerateInterpolator; 17 | import android.widget.AbsListView; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import com.ccb.arcselect.R; 22 | import com.ccb.arcselect.utils.CenterItemUtils; 23 | import com.ccb.arcselect.utils.TUtils; 24 | import com.ccb.arcselect.utils.UiUtils; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | 31 | /** 32 | * 滑动后自动选中居中的条目 类似 WheelView 33 | */ 34 | public class AutoSelectActivity extends AppCompatActivity { 35 | 36 | private TextView tv; 37 | private RecyclerView recyclerView; 38 | private MAdapter mAdapter; 39 | private int centerToTopDistance; //RecyclerView高度的一半 ,也就是控件中间位置到顶部的距离 , 40 | private int childViewHalfCount = 0; //当前RecyclerView一半最多可以存在几个Item 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_auto_select); 46 | recyclerView = findViewById(R.id.rv); 47 | tv = findViewById(R.id.tv); 48 | init(); 49 | 50 | } 51 | 52 | private void init() { 53 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 54 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 55 | @Override 56 | public void onGlobalLayout() { 57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 58 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 59 | } 60 | 61 | centerToTopDistance = recyclerView.getHeight() / 2; 62 | 63 | int childViewHeight = UiUtils.dip2px(AutoSelectActivity.this, 43); //43是当前已知的 Item的高度 64 | childViewHalfCount = (recyclerView.getHeight() / childViewHeight + 1) / 2; 65 | initData(); 66 | findView(); 67 | 68 | } 69 | }); 70 | recyclerView.postDelayed(new Runnable() { 71 | @Override 72 | public void run() { 73 | scrollToCenter(childViewHalfCount); 74 | } 75 | }, 100L); 76 | } 77 | 78 | private List mDatas; 79 | 80 | private void initData() { 81 | if (mDatas == null) mDatas = new ArrayList<>(); 82 | for (int i = 0; i < 55; i++) { 83 | mDatas.add("竖向条目" + i); 84 | } 85 | for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 86 | mDatas.add(0, null); 87 | } 88 | for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 89 | mDatas.add(null); 90 | } 91 | } 92 | 93 | private boolean isTouch = false; 94 | 95 | private List centerViewItems = new ArrayList<>(); 96 | private void findView() { 97 | mAdapter = new MAdapter(); 98 | recyclerView.setAdapter(mAdapter); 99 | 100 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 101 | @Override 102 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 103 | super.onScrollStateChanged(recyclerView, newState); 104 | if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { 105 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 106 | int fi = linearLayoutManager.findFirstVisibleItemPosition(); 107 | int la = linearLayoutManager.findLastVisibleItemPosition(); 108 | // int fi = linearLayoutManager.findFirstCompletelyVisibleItemPosition(); 109 | // int la = linearLayoutManager.findLastCompletelyVisibleItemPosition(); 110 | Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la); 111 | if (isTouch) { 112 | isTouch = false; 113 | //获取最中间的Item View 114 | int centerPositionDiffer = (la - fi) / 2; 115 | int centerChildViewPosition = fi + centerPositionDiffer; //获取当前所有条目中中间的一个条目索引 116 | centerViewItems.clear(); 117 | //遍历循环,获取到和中线相差最小的条目索引(精准查找最居中的条目) 118 | if (centerChildViewPosition != 0){ 119 | for (int i = centerChildViewPosition -1 ; i < centerChildViewPosition+2; i++) { 120 | View cView = recyclerView.getLayoutManager().findViewByPosition(i); 121 | int viewTop = cView.getTop()+(cView.getHeight()/2); 122 | centerViewItems.add(new CenterItemUtils.CenterViewItem(i ,Math.abs(centerToTopDistance - viewTop))); 123 | } 124 | 125 | CenterItemUtils.CenterViewItem centerViewItem = CenterItemUtils.getMinDifferItem(centerViewItems); 126 | centerChildViewPosition = centerViewItem.position; 127 | } 128 | 129 | scrollToCenter(centerChildViewPosition); 130 | // centerChildViewPosition = centerChildViewPosition < childViewHalfCount ? childViewHalfCount : centerChildViewPosition; 131 | // centerChildViewPosition = centerChildViewPosition <= mAdapter.getItemCount() - childViewHalfCount -1 ? centerChildViewPosition : mAdapter.getItemCount() - childViewHalfCount -1; 132 | // View childView = recyclerView.getLayoutManager().findViewByPosition(centerChildViewPosition); 133 | // Log.i("ccb", "滑动后中间View的索引: " + centerChildViewPosition); 134 | // 135 | // //把当前View移动到居中位置 136 | // if (childView == null) return; 137 | // int childVhalf = childView.getHeight() / 2; 138 | // int childViewTop = childView.getTop(); 139 | // int viewCTop = centerToTopDistance; 140 | // int smoothDistance = childViewTop - viewCTop + childVhalf; 141 | // Log.i("ccb", "居中位置距离顶部距离: " + viewCTop + "当前居中控件距离顶部距离: " + childViewTop); 142 | // Log.i("ccb", "滑动后再次移动距离: " + smoothDistance); 143 | // recyclerView.smoothScrollBy(0, smoothDistance); 144 | // Toast.makeText(AutoSelectActivity.this, "滑动后选中:" + mDatas.get(centerChildViewPosition), Toast.LENGTH_SHORT).show(); 145 | // mAdapter.setSelectPosition(centerChildViewPosition); 146 | } 147 | } 148 | } 149 | 150 | @Override 151 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 152 | super.onScrolled(recyclerView, dx, dy); 153 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 154 | recyclerView.getChildAt(i).invalidate(); 155 | } 156 | } 157 | }); 158 | 159 | recyclerView.setOnTouchListener(new View.OnTouchListener() { 160 | @Override 161 | public boolean onTouch(View view, MotionEvent motionEvent) { 162 | isTouch = true; 163 | return false; 164 | } 165 | }); 166 | } 167 | 168 | private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); 169 | /** 170 | * 移动指定索引到中心处 , 只可以移动可见区域的内容 171 | * @param position 172 | */ 173 | private void scrollToCenter(int position){ 174 | position = position < childViewHalfCount ? childViewHalfCount : position; 175 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 176 | 177 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 178 | View childView = linearLayoutManager.findViewByPosition(position); 179 | Log.i("ccb", "滑动后中间View的索引: " + position); 180 | //把当前View移动到居中位置 181 | if (childView == null) return; 182 | int childVhalf = childView.getHeight() / 2; 183 | int childViewTop = childView.getTop(); 184 | int viewCTop = centerToTopDistance; 185 | int smoothDistance = childViewTop - viewCTop + childVhalf; 186 | Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop 187 | + "\n当前居中控件距离顶部距离: " + childViewTop 188 | + "\n当前居中控件的一半高度: " + childVhalf 189 | + "\n滑动后再次移动距离: " + smoothDistance); 190 | recyclerView.smoothScrollBy(0, smoothDistance,decelerateInterpolator); 191 | mAdapter.setSelectPosition(position); 192 | 193 | tv.setText("当前选中:" + mDatas.get(position)); 194 | } 195 | 196 | 197 | class MAdapter extends RecyclerView.Adapter { 198 | @NonNull 199 | @Override 200 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 201 | return new VH(LayoutInflater.from(AutoSelectActivity.this).inflate(R.layout.item_auto_select, parent, false)); 202 | } 203 | 204 | @Override 205 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 206 | VH vh = (VH) holder; 207 | 208 | if (selectPosition == position) { 209 | vh.tv.setTextColor(getResources().getColor(R.color.textSelect)); 210 | } else { 211 | vh.tv.setTextColor(getResources().getColor(R.color.colorText)); 212 | } 213 | vh.tv.setText(mDatas.get(position)); 214 | final int fp = position; 215 | vh.itemView.setOnClickListener(new View.OnClickListener() { 216 | @Override 217 | public void onClick(View v) { 218 | scrollToCenter(fp); 219 | Toast.makeText(AutoSelectActivity.this, "点击" + mDatas.get(fp), Toast.LENGTH_SHORT).show(); 220 | } 221 | }); 222 | } 223 | 224 | private int selectPosition = -1; 225 | 226 | public void setSelectPosition(int cposition) { 227 | selectPosition = cposition; 228 | // notifyItemChanged(cposition); 229 | notifyDataSetChanged(); 230 | } 231 | 232 | @Override 233 | public int getItemCount() { 234 | return mDatas.size(); 235 | } 236 | 237 | class VH extends RecyclerView.ViewHolder { 238 | 239 | public TextView tv; 240 | 241 | public VH(@NonNull View itemView) { 242 | super(itemView); 243 | tv = itemView.findViewById(R.id.tv); 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/BottomArcActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.ViewTreeObserver; 12 | import android.view.animation.DecelerateInterpolator; 13 | import android.widget.AbsListView; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.appcompat.app.AppCompatActivity; 19 | import androidx.recyclerview.widget.LinearLayoutManager; 20 | import androidx.recyclerview.widget.RecyclerView; 21 | 22 | import com.ccb.arcselect.R; 23 | import com.ccb.arcselect.utils.CenterItemUtils; 24 | import com.ccb.arcselect.utils.UiUtils; 25 | import com.ccb.arcselect.view.HMatrixTranslateLayout; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | 31 | /** 32 | * 横向滑动后自动选中居中的条目 类似 横向WheelView 33 | */ 34 | public class BottomArcActivity extends AppCompatActivity { 35 | 36 | private final int CHILDVIEWSIZE = 180; 37 | private RecyclerView recyclerView; 38 | private MAdapter mAdapter; 39 | private int centerToLiftDistance; //RecyclerView款度的一半 ,也就是控件中间位置到左部的距离 , 40 | private int childViewHalfCount = 0; //当前RecyclerView一半最多可以存在几个Item 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_bottom_arc); 46 | recyclerView = findViewById(R.id.rv); 47 | init(); 48 | 49 | } 50 | 51 | private void init() { 52 | recyclerView.setLayoutManager(new LinearLayoutManager(this , LinearLayoutManager.HORIZONTAL , false)); 53 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 54 | @Override 55 | public void onGlobalLayout() { 56 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 57 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 58 | } 59 | 60 | centerToLiftDistance = recyclerView.getWidth() / 2; 61 | 62 | int childViewHeight = UiUtils.dip2px(BottomArcActivity.this, CHILDVIEWSIZE); //43是当前已知的 Item的高度 63 | childViewHalfCount = (recyclerView.getWidth() / childViewHeight + 1) / 2; 64 | initData(); 65 | findView(); 66 | 67 | } 68 | }); 69 | recyclerView.postDelayed(new Runnable() { 70 | @Override 71 | public void run() { 72 | scrollToCenter(childViewHalfCount); 73 | } 74 | }, 100L); 75 | } 76 | 77 | private List mDatas; 78 | 79 | private void initData() { 80 | if (mDatas == null) mDatas = new ArrayList<>(); 81 | for (int i = 0; i < 55; i++) { 82 | mDatas.add("条目" + i); 83 | } 84 | for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 85 | mDatas.add(0, null); 86 | } 87 | for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 88 | mDatas.add(null); 89 | } 90 | } 91 | 92 | private boolean isTouch = false; 93 | 94 | private List centerViewItems = new ArrayList<>(); 95 | private void findView() { 96 | mAdapter = new MAdapter(); 97 | recyclerView.setAdapter(mAdapter); 98 | 99 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 100 | @Override 101 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 102 | super.onScrollStateChanged(recyclerView, newState); 103 | if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { 104 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 105 | int fi = linearLayoutManager.findFirstVisibleItemPosition(); 106 | int la = linearLayoutManager.findLastVisibleItemPosition(); 107 | // int fi = linearLayoutManager.findFirstCompletelyVisibleItemPosition(); 108 | // int la = linearLayoutManager.findLastCompletelyVisibleItemPosition(); 109 | Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la); 110 | if (isTouch) { 111 | isTouch = false; 112 | //获取最中间的Item View 113 | int centerPositionDiffer = (la - fi) / 2; 114 | int centerChildViewPosition = fi + centerPositionDiffer; //获取当前所有条目中中间的一个条目索引 115 | centerViewItems.clear(); 116 | //遍历循环,获取到和中线相差最小的条目索引(精准查找最居中的条目) 117 | if (centerChildViewPosition != 0){ 118 | for (int i = centerChildViewPosition -1 ; i < centerChildViewPosition+2; i++) { 119 | View cView = recyclerView.getLayoutManager().findViewByPosition(i); 120 | int viewLeft = cView.getLeft()+(cView.getWidth()/2); 121 | centerViewItems.add(new CenterItemUtils.CenterViewItem(i ,Math.abs(centerToLiftDistance - viewLeft))); 122 | } 123 | 124 | CenterItemUtils.CenterViewItem centerViewItem = CenterItemUtils.getMinDifferItem(centerViewItems); 125 | centerChildViewPosition = centerViewItem.position; 126 | } 127 | 128 | scrollToCenter(centerChildViewPosition); 129 | } 130 | } 131 | } 132 | 133 | @Override 134 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 135 | super.onScrolled(recyclerView, dx, dy); 136 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 137 | recyclerView.getChildAt(i).invalidate(); 138 | } 139 | } 140 | }); 141 | 142 | recyclerView.setOnTouchListener(new View.OnTouchListener() { 143 | @Override 144 | public boolean onTouch(View view, MotionEvent motionEvent) { 145 | isTouch = true; 146 | return false; 147 | } 148 | }); 149 | } 150 | 151 | private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); 152 | /** 153 | * 移动指定索引到中心处 , 只可以移动可见区域的内容 154 | * @param position 155 | */ 156 | private void scrollToCenter(int position){ 157 | position = position < childViewHalfCount ? childViewHalfCount : position; 158 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 159 | 160 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 161 | View childView = linearLayoutManager.findViewByPosition(position); 162 | Log.i("ccb", "滑动后中间View的索引: " + position); 163 | //把当前View移动到居中位置 164 | if (childView == null) return; 165 | int childVhalf = childView.getWidth() / 2; 166 | int childViewLeft = childView.getLeft(); 167 | int viewCTop = centerToLiftDistance; 168 | int smoothDistance = childViewLeft - viewCTop + childVhalf; 169 | Log.i("ccb", "\n居中位置距离左部距离: " + viewCTop 170 | + "\n当前居中控件距离左部距离: " + childViewLeft 171 | + "\n当前居中控件的一半高度: " + childVhalf 172 | + "\n滑动后再次移动距离: " + smoothDistance); 173 | recyclerView.smoothScrollBy(smoothDistance, 0,decelerateInterpolator); 174 | mAdapter.setSelectPosition(position); 175 | 176 | // tv.setText("当前选中:" + mDatas.get(position)); 177 | } 178 | 179 | 180 | class MAdapter extends RecyclerView.Adapter { 181 | @NonNull 182 | @Override 183 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 184 | return new VH(LayoutInflater.from(BottomArcActivity.this).inflate(R.layout.item_bottom_arc, parent, false)); 185 | } 186 | 187 | @Override 188 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 189 | VH vh = (VH) holder; 190 | ((HMatrixTranslateLayout) vh.itemView).setParentWidth(recyclerView.getWidth()); 191 | 192 | if (selectPosition == position) { 193 | vh.tv.setTextColor(getResources().getColor(R.color.textSelect)); 194 | } else { 195 | vh.tv.setTextColor(getResources().getColor(R.color.white)); 196 | } 197 | if (TextUtils.isEmpty(mDatas.get(position))){ 198 | vh.itemView.setVisibility(View.INVISIBLE); 199 | }else { 200 | vh.itemView.setVisibility(View.VISIBLE); 201 | vh.tv.setText(mDatas.get(position)); 202 | } 203 | final int fp = position; 204 | vh.itemView.setOnClickListener(new View.OnClickListener() { 205 | @Override 206 | public void onClick(View v) { 207 | scrollToCenter(fp); 208 | Toast.makeText(BottomArcActivity.this, "点击" + mDatas.get(fp), Toast.LENGTH_SHORT).show(); 209 | } 210 | }); 211 | } 212 | 213 | private int selectPosition = -1; 214 | 215 | public void setSelectPosition(int cposition) { 216 | selectPosition = cposition; 217 | // notifyItemChanged(cposition); 218 | notifyDataSetChanged(); 219 | } 220 | 221 | @Override 222 | public int getItemCount() { 223 | return mDatas.size(); 224 | } 225 | 226 | class VH extends RecyclerView.ViewHolder { 227 | 228 | public TextView tv; 229 | 230 | public VH(@NonNull View itemView) { 231 | super(itemView); 232 | tv = itemView.findViewById(R.id.tv); 233 | } 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/CircleArcActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.ViewTreeObserver; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import com.ccb.arcselect.R; 18 | import com.ccb.arcselect.view.CircleMatrixView; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | public class CircleArcActivity extends AppCompatActivity { 24 | 25 | 26 | private RecyclerView recyclerView; 27 | private MAdapter mAdapter; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_rv); 33 | recyclerView = findViewById(R.id.rv); 34 | initData(); 35 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 36 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 37 | @Override 38 | public void onGlobalLayout() { 39 | findView(); 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 41 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 42 | } 43 | } 44 | }); 45 | 46 | 47 | } 48 | 49 | private List mDatas; 50 | private void initData() { 51 | if (mDatas == null) mDatas = new ArrayList<>(); 52 | for (int i = 0; i < 99; i++) { 53 | mDatas.add("CAR_Item"+i); 54 | } 55 | 56 | } 57 | 58 | private void findView() { 59 | mAdapter = new MAdapter(); 60 | recyclerView.setAdapter(mAdapter); 61 | 62 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 63 | @Override 64 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 65 | super.onScrollStateChanged(recyclerView, newState); 66 | } 67 | 68 | @Override 69 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 70 | super.onScrolled(recyclerView, dx, dy); 71 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 72 | recyclerView.getChildAt(i).invalidate(); 73 | } 74 | } 75 | }); 76 | 77 | } 78 | 79 | 80 | class MAdapter extends RecyclerView.Adapter { 81 | @NonNull 82 | @Override 83 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 84 | return new MAdapter.VH(LayoutInflater.from(CircleArcActivity.this).inflate(R.layout.item_circle, parent, false)); 85 | } 86 | 87 | @Override 88 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 89 | MAdapter.VH vh = (MAdapter.VH) holder; 90 | ((CircleMatrixView) vh.itemView).setParentHeight(recyclerView.getHeight()); 91 | vh.tv.setText(mDatas.get(position)); 92 | final int fp = position; 93 | vh.itemView.setOnClickListener(new View.OnClickListener() { 94 | @Override 95 | public void onClick(View v) { 96 | Toast.makeText(CircleArcActivity.this, "点击" +mDatas.get(fp), Toast.LENGTH_SHORT).show(); 97 | } 98 | }); 99 | } 100 | 101 | @Override 102 | public int getItemCount() { 103 | return mDatas.size(); 104 | } 105 | 106 | class VH extends RecyclerView.ViewHolder { 107 | 108 | public TextView tv; 109 | 110 | public VH(@NonNull View itemView) { 111 | super(itemView); 112 | tv = itemView.findViewById(R.id.tv); 113 | } 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/HorizontalSelectActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.ViewTreeObserver; 12 | import android.view.animation.DecelerateInterpolator; 13 | import android.widget.AbsListView; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.appcompat.app.AppCompatActivity; 19 | import androidx.recyclerview.widget.LinearLayoutManager; 20 | import androidx.recyclerview.widget.RecyclerView; 21 | 22 | import com.ccb.arcselect.R; 23 | import com.ccb.arcselect.utils.CenterItemUtils; 24 | import com.ccb.arcselect.utils.TUtils; 25 | import com.ccb.arcselect.utils.UiUtils; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | 31 | /** 32 | * 横向滑动后自动选中居中的条目 类似 横向WheelView 33 | */ 34 | public class HorizontalSelectActivity extends AppCompatActivity { 35 | 36 | private final int CHILDVIEWSIZE = 100; 37 | private RecyclerView recyclerView; 38 | private TextView tv; 39 | private MAdapter mAdapter; 40 | private int centerToLiftDistance; //RecyclerView款度的一半 ,也就是控件中间位置到左部的距离 , 41 | private int childViewHalfCount = 0; //当前RecyclerView一半最多可以存在几个Item 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_auto_select_h); 47 | recyclerView = findViewById(R.id.rv); 48 | tv = findViewById(R.id.tv); 49 | init(); 50 | 51 | } 52 | 53 | private void init() { 54 | recyclerView.setLayoutManager(new LinearLayoutManager(this , LinearLayoutManager.HORIZONTAL , false)); 55 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 56 | @Override 57 | public void onGlobalLayout() { 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 59 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 60 | } 61 | 62 | centerToLiftDistance = recyclerView.getWidth() / 2; 63 | 64 | int childViewHeight = UiUtils.dip2px(HorizontalSelectActivity.this, CHILDVIEWSIZE); //43是当前已知的 Item的高度 65 | childViewHalfCount = (recyclerView.getWidth() / childViewHeight + 1) / 2; 66 | initData(); 67 | findView(); 68 | 69 | } 70 | }); 71 | recyclerView.postDelayed(new Runnable() { 72 | @Override 73 | public void run() { 74 | scrollToCenter(childViewHalfCount); 75 | } 76 | }, 100L); 77 | } 78 | 79 | private List mDatas; 80 | 81 | private void initData() { 82 | if (mDatas == null) mDatas = new ArrayList<>(); 83 | for (int i = 0; i < 55; i++) { 84 | mDatas.add("条目" + i); 85 | } 86 | for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 87 | mDatas.add(0, null); 88 | } 89 | for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 90 | mDatas.add(null); 91 | } 92 | } 93 | 94 | private boolean isTouch = false; 95 | 96 | private List centerViewItems = new ArrayList<>(); 97 | private void findView() { 98 | mAdapter = new MAdapter(); 99 | recyclerView.setAdapter(mAdapter); 100 | 101 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 102 | @Override 103 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 104 | super.onScrollStateChanged(recyclerView, newState); 105 | if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { 106 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 107 | int fi = linearLayoutManager.findFirstVisibleItemPosition(); 108 | int la = linearLayoutManager.findLastVisibleItemPosition(); 109 | // int fi = linearLayoutManager.findFirstCompletelyVisibleItemPosition(); 110 | // int la = linearLayoutManager.findLastCompletelyVisibleItemPosition(); 111 | Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la); 112 | if (isTouch) { 113 | isTouch = false; 114 | //获取最中间的Item View 115 | int centerPositionDiffer = (la - fi) / 2; 116 | int centerChildViewPosition = fi + centerPositionDiffer; //获取当前所有条目中中间的一个条目索引 117 | centerViewItems.clear(); 118 | //遍历循环,获取到和中线相差最小的条目索引(精准查找最居中的条目) 119 | if (centerChildViewPosition != 0){ 120 | for (int i = centerChildViewPosition -1 ; i < centerChildViewPosition+2; i++) { 121 | View cView = recyclerView.getLayoutManager().findViewByPosition(i); 122 | int viewLeft = cView.getLeft()+(cView.getWidth()/2); 123 | centerViewItems.add(new CenterItemUtils.CenterViewItem(i ,Math.abs(centerToLiftDistance - viewLeft))); 124 | } 125 | 126 | CenterItemUtils.CenterViewItem centerViewItem = CenterItemUtils.getMinDifferItem(centerViewItems); 127 | centerChildViewPosition = centerViewItem.position; 128 | } 129 | 130 | scrollToCenter(centerChildViewPosition); 131 | } 132 | } 133 | } 134 | 135 | @Override 136 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 137 | super.onScrolled(recyclerView, dx, dy); 138 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 139 | recyclerView.getChildAt(i).invalidate(); 140 | } 141 | } 142 | }); 143 | 144 | recyclerView.setOnTouchListener(new View.OnTouchListener() { 145 | @Override 146 | public boolean onTouch(View view, MotionEvent motionEvent) { 147 | isTouch = true; 148 | return false; 149 | } 150 | }); 151 | } 152 | 153 | private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); 154 | /** 155 | * 移动指定索引到中心处 , 只可以移动可见区域的内容 156 | * @param position 157 | */ 158 | private void scrollToCenter(int position){ 159 | position = position < childViewHalfCount ? childViewHalfCount : position; 160 | position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; 161 | 162 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 163 | View childView = linearLayoutManager.findViewByPosition(position); 164 | Log.i("ccb", "滑动后中间View的索引: " + position); 165 | //把当前View移动到居中位置 166 | if (childView == null) return; 167 | int childVhalf = childView.getWidth() / 2; 168 | int childViewLeft = childView.getLeft(); 169 | int viewCTop = centerToLiftDistance; 170 | int smoothDistance = childViewLeft - viewCTop + childVhalf; 171 | Log.i("ccb", "\n居中位置距离左部距离: " + viewCTop 172 | + "\n当前居中控件距离左部距离: " + childViewLeft 173 | + "\n当前居中控件的一半高度: " + childVhalf 174 | + "\n滑动后再次移动距离: " + smoothDistance); 175 | recyclerView.smoothScrollBy(smoothDistance, 0,decelerateInterpolator); 176 | mAdapter.setSelectPosition(position); 177 | 178 | tv.setText("当前选中:" + mDatas.get(position)); 179 | } 180 | 181 | 182 | class MAdapter extends RecyclerView.Adapter { 183 | @NonNull 184 | @Override 185 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 186 | return new VH(LayoutInflater.from(HorizontalSelectActivity.this).inflate(R.layout.item_auto_select_h, parent, false)); 187 | } 188 | 189 | @Override 190 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 191 | VH vh = (VH) holder; 192 | 193 | if (selectPosition == position) { 194 | vh.tv.setTextColor(getResources().getColor(R.color.textSelect)); 195 | } else { 196 | vh.tv.setTextColor(getResources().getColor(R.color.white)); 197 | } 198 | if (TextUtils.isEmpty(mDatas.get(position))){ 199 | vh.itemView.setVisibility(View.INVISIBLE); 200 | }else { 201 | vh.itemView.setVisibility(View.VISIBLE); 202 | vh.tv.setText(mDatas.get(position)); 203 | } 204 | final int fp = position; 205 | vh.itemView.setOnClickListener(new View.OnClickListener() { 206 | @Override 207 | public void onClick(View v) { 208 | scrollToCenter(fp); 209 | Toast.makeText(HorizontalSelectActivity.this, "点击" + mDatas.get(fp), Toast.LENGTH_SHORT).show(); 210 | } 211 | }); 212 | } 213 | 214 | private int selectPosition = -1; 215 | 216 | public void setSelectPosition(int cposition) { 217 | selectPosition = cposition; 218 | // notifyItemChanged(cposition); 219 | notifyDataSetChanged(); 220 | } 221 | 222 | @Override 223 | public int getItemCount() { 224 | return mDatas.size(); 225 | } 226 | 227 | class VH extends RecyclerView.ViewHolder { 228 | 229 | public TextView tv; 230 | 231 | public VH(@NonNull View itemView) { 232 | super(itemView); 233 | tv = itemView.findViewById(R.id.tv); 234 | } 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/ui/PadingArcActivity.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.ui; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.ViewTreeObserver; 9 | import android.widget.RelativeLayout; 10 | import android.widget.TextView; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.appcompat.app.AppCompatActivity; 14 | import androidx.recyclerview.widget.LinearLayoutManager; 15 | import androidx.recyclerview.widget.RecyclerView; 16 | 17 | import com.ccb.arcselect.R; 18 | import com.ccb.arcselect.utils.TUtils; 19 | import com.ccb.arcselect.utils.UiUtils; 20 | 21 | 22 | /** 23 | * 使用Padding实现弧形列表 24 | */ 25 | public class PadingArcActivity extends AppCompatActivity { 26 | 27 | 28 | private RecyclerView recyclerView; 29 | private int recyclerViewHeight; 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_rv); 34 | recyclerView = findViewById(R.id.rv); 35 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 36 | 37 | recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 38 | @Override 39 | public void onGlobalLayout() { 40 | recyclerViewHeight = recyclerView.getHeight(); 41 | setData(); 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 43 | recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 44 | } 45 | } 46 | }); 47 | 48 | } 49 | 50 | private void setData() { 51 | recyclerView.setAdapter(new RecyclerView.Adapter() { 52 | @NonNull 53 | @Override 54 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 55 | return new VH(LayoutInflater.from(PadingArcActivity.this).inflate( R.layout.item_pading,parent,false)); 56 | } 57 | 58 | @Override 59 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 60 | VH vh = (VH) holder; 61 | RelativeLayout mv = (RelativeLayout) vh.itemView; 62 | mv.setPadding(calculateTranslate(mv.getTop() , recyclerViewHeight) , 0 ,0 ,0 ); 63 | vh.tv.setText("你好"+position+"索引"); 64 | final int fp = position; 65 | vh.itemView.setOnClickListener(new View.OnClickListener() { 66 | @Override 67 | public void onClick(View v) { 68 | TUtils.show(PadingArcActivity.this , "你好"+fp+"索引"); 69 | } 70 | }); 71 | } 72 | 73 | @Override 74 | public int getItemCount() { 75 | return 100; 76 | } 77 | 78 | class VH extends RecyclerView.ViewHolder{ 79 | 80 | public TextView tv; 81 | public VH(@NonNull View itemView) { 82 | super(itemView); 83 | tv = itemView.findViewById(R.id.tv); 84 | } 85 | } 86 | }); 87 | 88 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 89 | @Override 90 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 91 | super.onScrollStateChanged(recyclerView, newState); 92 | } 93 | 94 | @Override 95 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 96 | super.onScrolled(recyclerView, dx, dy); 97 | for (int i = 0; i < recyclerView.getChildCount(); i++) { 98 | int pad = calculateTranslate(recyclerView.getChildAt(i).getTop() , recyclerViewHeight); 99 | recyclerView.getChildAt(i).setPadding( pad, 0 ,0 ,0 ); 100 | } 101 | } 102 | }); 103 | } 104 | 105 | private int calculateTranslate(int top, int h) { 106 | int result = 0; 107 | h = h - UiUtils.dip2px(this , 60); //减去当前控件的高度,(60是已知当前Item的高度) 108 | int hh = h/2; 109 | result = Math.abs(hh - top); 110 | result = hh - result; 111 | return result/2; 112 | } 113 | 114 | 115 | // @Deprecated 116 | // public void changeGroupFlag(Object obj) throws Exception { 117 | // Field[] f = obj.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredFields(); // 获得成员映射数组 118 | // for (Field tem : f) { 119 | // if (tem.getName().equals("mGroupFlags")) { 120 | // tem.setAccessible(true); 121 | // Integer mGroupFlags = (Integer) tem.get(obj); 122 | // int newGroupFlags = mGroupFlags & 0xfffff8; 123 | // tem.set(obj, newGroupFlags); 124 | // } 125 | // } 126 | // } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/utils/CenterItemUtils.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.utils; 2 | 3 | import java.util.List; 4 | 5 | public class CenterItemUtils { 6 | 7 | /** 8 | * 计算距离中间最近的一个ItemView 9 | * @param itemHeights 10 | * @return 11 | */ 12 | public static CenterViewItem getMinDifferItem(List itemHeights){ 13 | CenterViewItem minItem = itemHeights.get(0); //默认第一个是最小差值 14 | for (int i = 0; i < itemHeights.size(); i++) { 15 | //遍历获取最小差值 16 | if (itemHeights.get(i).differ <= minItem.differ){ 17 | minItem = itemHeights.get(i); 18 | } 19 | } 20 | return minItem; 21 | } 22 | 23 | // public static void main(String[] a){ 24 | // 25 | // CenterViewItem i = getMinDifferItem(Arrays.asList( 26 | // new CenterViewItem(2 , 39) 27 | // ,new CenterViewItem(3 , 3) 28 | // ,new CenterViewItem(1 , 9) 29 | // ,new CenterViewItem(4 , 449))); 30 | // System.out.println("position:"+i.position+" height:"+i.differ); 31 | // } 32 | 33 | public static class CenterViewItem{ 34 | public CenterViewItem(int position, int differ) { 35 | this.position = position; //当前Item索引 36 | this.differ = differ; //当前item和居中位置的差值 37 | } 38 | 39 | public int position; 40 | public int differ; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/utils/TUtils.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.utils; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | public class TUtils { 7 | 8 | private static Toast t; 9 | 10 | public static void show(Context c , String s){ 11 | if (t == null)t = Toast.makeText(c,s,Toast.LENGTH_SHORT); 12 | t.setText(s); 13 | t.show(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/utils/UiUtils.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.utils; 2 | 3 | import android.content.Context; 4 | 5 | public class UiUtils { 6 | 7 | /** 8 | * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 9 | */ 10 | public static int dip2px(Context context, float dpValue) { 11 | final float scale = context.getResources().getDisplayMetrics().density; 12 | return (int) (dpValue * scale + 0.5f); 13 | } 14 | 15 | /** 16 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 17 | */ 18 | public static int px2dip(Context context, float pxValue) { 19 | final float scale = context.getResources().getDisplayMetrics().density; 20 | return (int) (pxValue / scale + 0.5f); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/view/CircleMatrixView.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Matrix; 6 | import android.util.AttributeSet; 7 | import android.widget.LinearLayout; 8 | 9 | public class CircleMatrixView extends LinearLayout { 10 | private int h = 0; 11 | private float fullAngelFactor = 30f; 12 | private float fullScaleFactor=1; 13 | public CircleMatrixView(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | } 16 | 17 | public void setParentHeight(int height) { 18 | h = height; 19 | } 20 | 21 | @Override 22 | protected void dispatchDraw(Canvas canvas) { 23 | canvas.save(); 24 | int top = getTop(); 25 | 26 | float rotate = calculateAngel(top, h); 27 | float scale = calcuylateScale(top, h); 28 | 29 | Matrix m = canvas.getMatrix(); 30 | m.preTranslate(-2 / getWidth(), -2 / getHeight()); 31 | m.postScale(scale, scale); 32 | m.postTranslate(2 / getWidth(), 2 / getHeight()); 33 | m.postRotate(rotate); 34 | canvas.concat(m); 35 | super.dispatchDraw(canvas); 36 | canvas.restore(); 37 | } 38 | 39 | @Override 40 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 41 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 42 | } 43 | 44 | private float calculateAngel(int top, int h) { 45 | float result = 0f; 46 | if (top < h / 2f) { 47 | result = (top - (h / 2f)) / (h / 2f) * fullAngelFactor; 48 | } else if (top > h / 2f) { 49 | result = (top - (h / 2f)) / (h / 2f) * fullAngelFactor; 50 | } 51 | return result; 52 | } 53 | 54 | private float calcuylateScale(int top, int h) { 55 | float result = 0f; 56 | 57 | result = (1f - 1f/2f*Math.abs((top - h / 2f)) / (h / 2f)) * fullScaleFactor; 58 | return result; 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/view/CircleRelativeLayout.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.widget.RelativeLayout; 9 | 10 | import com.ccb.arcselect.R; 11 | 12 | public class CircleRelativeLayout extends RelativeLayout { 13 | private int color; 14 | private int[] colors; 15 | private int alpha; 16 | public CircleRelativeLayout(Context context) { 17 | super(context); 18 | } 19 | public CircleRelativeLayout(Context context, AttributeSet attrs) { 20 | super(context,attrs); 21 | init(context,attrs); 22 | setWillNotDraw(false); 23 | } 24 | private void init(Context context, AttributeSet attrs) { 25 | TypedArray array = context.obtainStyledAttributes(attrs, 26 | R.styleable.CircleRelativeLayoutLayout); 27 | color = array.getColor(R.styleable.CircleRelativeLayoutLayout_background_color,0X0000000); 28 | alpha = array.getInteger(R.styleable.CircleRelativeLayoutLayout_background_alpha,100); 29 | setColors(); 30 | array.recycle(); 31 | } 32 | @Override 33 | protected void onDraw(Canvas canvas) { //构建圆形 34 | int width = getMeasuredWidth(); 35 | Paint mPaint = new Paint(); 36 | mPaint.setARGB(alpha,colors[0],colors[1],colors[2]); 37 | mPaint.setAntiAlias(true); 38 | float cirX = width / 2; 39 | float cirY = width / 2; 40 | float radius = width / 2; 41 | canvas.drawCircle(cirX, cirY, radius, mPaint); 42 | super.onDraw(canvas); 43 | } 44 | 45 | public void setColor(int color) { //设置背景色 46 | this.color = color; 47 | setColors(); 48 | invalidate(); 49 | } 50 | 51 | public void setAlhpa(int alhpa) { //设置透明度 52 | this.alpha = alhpa; 53 | invalidate(); 54 | } 55 | 56 | 57 | public void setColors() { 58 | int red = (color & 0xff0000) >> 16; 59 | int green = (color & 0x00ff00) >> 8; 60 | int blue = (color & 0x0000ff); 61 | this.colors = new int[]{red,green,blue}; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/view/HMatrixTranslateLayout.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Matrix; 6 | import android.util.AttributeSet; 7 | import android.widget.LinearLayout; 8 | 9 | public class HMatrixTranslateLayout extends LinearLayout { 10 | private int parentWidth = 0; 11 | private int topOffset = 0; 12 | public HMatrixTranslateLayout(Context context, AttributeSet attrs) { 13 | super(context, attrs); 14 | } 15 | 16 | public void setParentWidth(int width) { 17 | parentWidth = width; 18 | } 19 | 20 | @Override 21 | protected void dispatchDraw(Canvas canvas) { 22 | canvas.save(); 23 | if (topOffset == 0) { 24 | topOffset = getWidth() / 2; 25 | } 26 | int left = getLeft()+topOffset; 27 | 28 | float tran = calculateTranslate(left , parentWidth); 29 | 30 | Matrix m = canvas.getMatrix(); 31 | m.setTranslate(0,tran); 32 | canvas.concat(m); 33 | super.dispatchDraw(canvas); 34 | canvas.restore(); 35 | } 36 | 37 | @Override 38 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 39 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 40 | } 41 | 42 | 43 | private float calculateTranslate(int left, int w) { 44 | float result = 0f; 45 | int hh = w/2; 46 | result = Math.abs(left - hh); 47 | // result = Math.abs(left - hh)*-1; 48 | return (float) (result/3.14); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/ccb/arcselect/view/MatrixTranslateLayout.java: -------------------------------------------------------------------------------- 1 | package com.ccb.arcselect.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Matrix; 6 | import android.util.AttributeSet; 7 | import android.widget.LinearLayout; 8 | 9 | public class MatrixTranslateLayout extends LinearLayout { 10 | private int parentHeight = 0; 11 | private int topOffset = 0; 12 | public MatrixTranslateLayout(Context context, AttributeSet attrs) { 13 | super(context, attrs); 14 | } 15 | 16 | public void setParentHeight(int height) { 17 | parentHeight = height; 18 | } 19 | 20 | @Override 21 | protected void dispatchDraw(Canvas canvas) { 22 | canvas.save(); 23 | if (topOffset == 0) { 24 | topOffset = getHeight() / 2; 25 | } 26 | int top = getTop()+topOffset; 27 | 28 | float tran = calculateTranslate(top , parentHeight); 29 | 30 | Matrix m = canvas.getMatrix(); 31 | m.setTranslate(tran,0); 32 | canvas.concat(m); 33 | super.dispatchDraw(canvas); 34 | canvas.restore(); 35 | } 36 | 37 | @Override 38 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 39 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 40 | } 41 | 42 | 43 | private float calculateTranslate(int top, int h) { 44 | float result = 0f; 45 | int hh = h/2; 46 | result = Math.abs(top - hh)*-1; 47 | return (float) (result/2); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CuiChenbo/ArcSelectList/b1fe8a123bad21eab89f3b7359792137ffa194cd/app/src/main/res/drawable/ic_location.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_arc_select_r.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 20 | 27 | 28 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 50 | 55 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_auto_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 25 | 26 | 30 | 31 | 36 | 37 | 38 | 39 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_auto_select_h.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 21 | 22 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_bottom_arc.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_rv.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_select_arc.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | 20 | 21 | 22 | 23 | 28 | 33 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | 14 |