├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── loopswitch
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable
│ │ ├── image_shader.png
│ │ ├── point1.xml
│ │ ├── point2.xml
│ │ ├── point3.xml
│ │ ├── point4.xml
│ │ └── point5.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── content_main.xml
│ │ ├── loopview_item_view0.xml
│ │ ├── loopview_item_view1.xml
│ │ ├── loopview_item_view2.xml
│ │ ├── loopview_item_view4.xml
│ │ └── loopview_item_view5.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── image0.png
│ │ ├── image1.png
│ │ ├── image2.png
│ │ ├── image3.png
│ │ ├── image4.png
│ │ ├── image5.png
│ │ ├── image6.png
│ │ └── image7.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── loopswitch
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── image
├── shot1.jpg
└── shot2.jpg
├── loopplay
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── slidemove
│ │ └── library
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── slidemove
│ │ │ └── library
│ │ │ ├── listener
│ │ │ ├── OnItemClickListener.java
│ │ │ ├── OnItemSelectedListener.java
│ │ │ └── OnLoopViewTouchListener.java
│ │ │ └── view
│ │ │ ├── LoopRotarySwitchView.java
│ │ │ └── LoopRotarySwitchViewHandler.java
│ └── res
│ │ └── values
│ │ ├── attrs.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── slidemove
│ └── library
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 1.8
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 效果图
2 | 
3 |
4 |
5 | ## 介绍
6 |
7 | *1. 3d旋转
8 | 可以无限循环,自动旋转,停靠的3D旋转布局控件,无需编写代码,直接在布局中加入自己的布局即可。
9 | 控件继承相对布局RelativeLayout直接可以当做布局使用。
10 |
11 | *2.支持自动旋转
12 |
13 | *3.可直接在xml添加元素即可添加列数据。也可以动态代码添加view
14 |
15 | *4.优良的兼容性,和可以自己尺寸控制
16 |
17 | *5.添加了点击切换和点击中间监听,适合目前app的需求
18 |
19 | *6.优化控件点击切换效果,使用更加的方法实用
20 |
21 | ## 如何使用
22 |
23 |
24 | 在你的项目Gradle 直接引入库文件
25 |
26 |
27 | implementation project(':loopplay')
28 |
29 |
30 |
31 | ## 配置view
32 |
33 | 布局xml里:
34 |
35 |
42 |
46 |
50 |
54 |
58 |
59 |
60 |
61 | 代码设置:
62 |
63 | mLoopRotarySwitchView
64 | .setR(300)//设置R的大小
65 | .setAutoRotation(true)//是否自动切换
66 | .setAutoScrollDirection(LoopRotarySwitchView.AutoScrollDirection.left)//切换方向
67 | .setAutoRotationTime(2000);//自动切换的时间 单位毫秒
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion "28.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.example.looprotaryswitch"
9 | minSdkVersion 14
10 | targetSdkVersion 28
11 | versionCode 1
12 | versionName "1.0.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(include: ['*.jar'], dir: 'libs')
24 | testImplementation 'junit:junit:4.12'
25 | implementation 'com.android.support:appcompat-v7:28.image0.image0'
26 | implementation 'com.android.support:design:28.0.0'
27 | // compile 'com.dalong:loopview:image1.image0.image3'
28 | implementation project(':loopplay')
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/zhouweilong/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/loopswitch/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.loopswitch;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.util.DisplayMetrics;
7 | import android.util.Log;
8 | import android.view.View;
9 | import android.view.WindowManager;
10 | import android.widget.CheckBox;
11 | import android.widget.CompoundButton;
12 | import android.widget.SeekBar;
13 | import android.widget.Switch;
14 |
15 | import com.slidemove.library.listener.OnItemSelectedListener;
16 | import com.slidemove.library.view.LoopRotarySwitchView;
17 |
18 | public class MainActivity extends AppCompatActivity {
19 |
20 | private static final String TAG = MainActivity.class.getSimpleName();
21 | private LoopRotarySwitchView mLoopRotarySwitchView;
22 |
23 | private int width;
24 | private SeekBar mSeekBarX, mSeekBarZ;
25 | private CheckBox mCheckbox;
26 | private Switch mSwitchLeftright;
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_main);
32 |
33 | initView();
34 | initLoopRotarySwitchView();
35 | initLinstener();
36 | }
37 |
38 | private void initLinstener() {
39 | /**
40 | * 选中回调
41 | */
42 | mLoopRotarySwitchView.setOnItemSelectedListener(new OnItemSelectedListener() {
43 | @Override
44 | public void selected(int position, View view) {
45 | }
46 | });
47 | /**
48 | * 设置子view的x坐标
49 | */
50 | mSeekBarX.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
51 | @Override
52 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
53 | int i = seekBar.getMax() / 2;
54 | Log.e(TAG, "i----" + i + " ,progress----" + progress+ " ,progress----> " + (progress-i));
55 | mLoopRotarySwitchView.setLoopRotationX(progress - seekBar.getMax() / 2);
56 | mLoopRotarySwitchView.initView();
57 | }
58 |
59 | @Override
60 | public void onStartTrackingTouch(SeekBar seekBar) {
61 | }
62 |
63 | @Override
64 | public void onStopTrackingTouch(SeekBar seekBar) {
65 | }
66 | });
67 | /**
68 | * 设置子view的z坐标
69 | */
70 | mSeekBarZ.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
71 | @Override
72 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
73 | mLoopRotarySwitchView.setLoopRotationZ(progress - seekBar.getMax() / 2);
74 | mLoopRotarySwitchView.initView();
75 | }
76 |
77 | @Override
78 | public void onStartTrackingTouch(SeekBar seekBar) {
79 |
80 | }
81 |
82 | @Override
83 | public void onStopTrackingTouch(SeekBar seekBar) {
84 | }
85 | });
86 | /**
87 | * 设置是否自动旋转
88 | */
89 | mCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
90 | @Override
91 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
92 | mLoopRotarySwitchView.setAutoRotation(isChecked);//启动LoopViewPager自动切换
93 | }
94 | });
95 | /**
96 | * 设置向左还是向右自动旋转
97 | */
98 | mSwitchLeftright.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
99 | @Override
100 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
101 | mLoopRotarySwitchView.setAutoScrollDirection(isChecked ? LoopRotarySwitchView.AutoScrollDirection.left
102 | : LoopRotarySwitchView.AutoScrollDirection.right);
103 | }
104 | });
105 | }
106 |
107 | /**
108 | * 设置LoopRotarySwitchView
109 | */
110 | private void initLoopRotarySwitchView() {
111 | mLoopRotarySwitchView
112 | .setR(width / 3)//设置R的大小
113 | .setAutoRotation(false)//是否自动切换
114 | .setAutoScrollDirection(LoopRotarySwitchView.AutoScrollDirection.left)
115 | .setAutoRotationTime(1500);//自动切换的时间 单位毫秒
116 | }
117 |
118 | /**
119 | * 初始化布局
120 | */
121 | private void initView() {
122 | mLoopRotarySwitchView = (LoopRotarySwitchView) findViewById(R.id.mLoopRotarySwitchView);
123 | mSeekBarX = (SeekBar) findViewById(R.id.seekBarX);
124 | mSeekBarZ = (SeekBar) findViewById(R.id.seekBarZ);
125 | mCheckbox = (CheckBox) findViewById(R.id.checkbox);
126 | mSwitchLeftright = (Switch) findViewById(R.id.switchLeftright);
127 | int i = mSeekBarX.getMax() / 2;
128 | Log.e(TAG, "i----" + i);
129 | // mSeekBarX.setProgress(i);
130 | int progress = mSeekBarX.getProgress();
131 | mLoopRotarySwitchView.setLoopRotationX(progress-i);
132 | // mSeekBarX.setProgress(80);
133 | mSeekBarZ.setProgress(mSeekBarZ.getMax() / 2);
134 |
135 | DisplayMetrics dm = new DisplayMetrics();
136 | WindowManager windowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
137 | windowManager.getDefaultDisplay().getMetrics(dm);
138 | width = dm.widthPixels;
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/image_shader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/drawable/image_shader.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/point1.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/point2.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/point3.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/point4.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/point5.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
18 |
22 |
26 |
30 |
34 |
38 |
42 |
46 |
50 |
54 |
55 |
56 |
57 |
58 |
59 |
65 |
66 |
73 |
79 |
86 |
91 |
94 |
102 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loopview_item_view0.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
30 |
31 |
32 |
33 |
34 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loopview_item_view1.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
30 |
31 |
32 |
33 |
34 |
35 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loopview_item_view2.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loopview_item_view4.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/loopview_item_view5.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image0.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image1.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image2.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image3.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image4.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image5.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image6.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xhdpi/image7.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 | >
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
8 | #803f99f6
9 | #8098bf50
10 | #80f67b7b
11 | #80FF4081
12 | #80303F9F
13 | #fffffffb
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LoopRotarySwitch
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/loopswitch/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.loopswitch;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.3.2'
11 | // classpath 'com.novoda:bintray-release:image0.image3.image4'
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | google()
21 | mavenCentral()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jan 21 09:44:19 CST 2019
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-4.10.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 |
--------------------------------------------------------------------------------
/image/shot1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/image/shot1.jpg
--------------------------------------------------------------------------------
/image/shot2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shenpeibao/Loop3DRotation-master/6838442ac8e860a5b01c94b66a2851e008144800/image/shot2.jpg
--------------------------------------------------------------------------------
/loopplay/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/loopplay/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | android {
3 | compileSdkVersion 28
4 | buildToolsVersion "28.0.3"
5 |
6 | defaultConfig {
7 | minSdkVersion 14
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0.0"
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 | }
19 |
20 | dependencies {
21 | api fileTree(dir: 'libs', include: ['*.jar'])
22 | testImplementation 'junit:junit:4.12'
23 | api 'com.android.support:appcompat-v7:28.0.0'
24 | }
25 |
26 |
27 |
--------------------------------------------------------------------------------
/loopplay/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 /android/tools/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 |
--------------------------------------------------------------------------------
/loopplay/src/androidTest/java/com/slidemove/library/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/loopplay/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/loopplay/src/main/java/com/slidemove/library/listener/OnItemClickListener.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * 选择回调接口
7 | */
8 | public interface OnItemClickListener {
9 | void onItemClick(int item, View view);
10 | }
11 |
--------------------------------------------------------------------------------
/loopplay/src/main/java/com/slidemove/library/listener/OnItemSelectedListener.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library.listener;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * 选择回调接口
7 | */
8 | public interface OnItemSelectedListener {
9 | void selected(int item, View view);
10 | }
11 |
--------------------------------------------------------------------------------
/loopplay/src/main/java/com/slidemove/library/listener/OnLoopViewTouchListener.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library.listener;
2 |
3 | import android.view.MotionEvent;
4 |
5 | /**
6 | * 选择回调接口
7 | */
8 | public interface OnLoopViewTouchListener {
9 | void onTouch(MotionEvent event);
10 | }
11 |
--------------------------------------------------------------------------------
/loopplay/src/main/java/com/slidemove/library/view/LoopRotarySwitchView.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library.view;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.annotation.SuppressLint;
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.util.AttributeSet;
9 | import android.view.GestureDetector;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.view.animation.DecelerateInterpolator;
13 | import android.widget.RelativeLayout;
14 |
15 | import com.slidemove.library.R;
16 | import com.slidemove.library.listener.OnItemClickListener;
17 | import com.slidemove.library.listener.OnItemSelectedListener;
18 | import com.slidemove.library.listener.OnLoopViewTouchListener;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.Comparator;
23 | import java.util.List;
24 | import java.util.ListIterator;
25 |
26 | /***
27 | * 水平旋转轮播控件
28 | */
29 | public class LoopRotarySwitchView extends RelativeLayout {
30 |
31 | private final static int LoopR = 200;
32 |
33 | private final static int vertical = 0;//竖直
34 |
35 | private final static int horizontal = 1;//水平
36 |
37 | private int mOrientation = horizontal;//方向
38 |
39 | private Context mContext;//上下文
40 |
41 | private ValueAnimator restAnimator = null;//回位动画
42 |
43 | private ValueAnimator rAnimation = null;//半径动画
44 |
45 | private ValueAnimator zAnimation = null;
46 |
47 | private ValueAnimator xAnimation = null;
48 |
49 | private int loopRotationX = 0, loopRotationZ = 0;//x轴旋转和轴旋转,y轴无效果
50 |
51 | private GestureDetector mGestureDetector = null;//手势类
52 |
53 | private int selectItem = 0;//当前选择项
54 |
55 | private int size = 0;//个数
56 |
57 | private float r = LoopR;//半径
58 |
59 | private float multiple = 2f;//倍数
60 |
61 | private float distance = multiple * r;//camera和观察的旋转物体距离, 距离越长,最大物体和最小物体比例越不明显
62 |
63 | private float angle = 0; //旋转的角度
64 |
65 | private float last_angle = 0; //最后的角度,用来记录上一次取消touch之后的角度
66 |
67 | private boolean autoRotation = false;//自动旋转
68 |
69 | private boolean touching = false;//正在触摸
70 |
71 | private AutoScrollDirection autoRotatinDirection = AutoScrollDirection.left; //默认自动滚动是从右往左
72 |
73 | private List views = new ArrayList();//子view引用列表
74 |
75 | private OnItemSelectedListener onItemSelectedListener = null;//选择事件接口
76 |
77 | private OnLoopViewTouchListener onLoopViewTouchListener = null;//选择事件接口
78 |
79 | private OnItemClickListener onItemClickListener = null;//被点击的回调
80 |
81 | private boolean isCanClickListener = true;//是否可以点击回调
82 |
83 | private float x;//移动的x是否符合回调点击事件
84 |
85 | private float limitX = 30;//滑动倒最低30
86 |
87 | public enum AutoScrollDirection {
88 | left, right
89 | }
90 |
91 | /**
92 | * 构造方法
93 | *
94 | * @param context
95 | */
96 | public LoopRotarySwitchView(Context context) {
97 | this(context, null);
98 | }
99 |
100 | /**
101 | * 构造方法
102 | *
103 | * @param context
104 | * @param attrs
105 | */
106 | public LoopRotarySwitchView(Context context, AttributeSet attrs) {
107 | this(context, attrs, 0);
108 | }
109 |
110 | /**
111 | * 构造方法
112 | *
113 | * @param context
114 | * @param attrs
115 | * @param defStyleAttr
116 | */
117 | public LoopRotarySwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
118 | super(context, attrs, defStyleAttr);
119 | this.mContext = context;
120 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoopRotarySwitchView);
121 | mOrientation = typedArray.getInt(R.styleable.LoopRotarySwitchView_orientation, horizontal);
122 | autoRotation = typedArray.getBoolean(R.styleable.LoopRotarySwitchView_autoRotation, false);
123 | r = typedArray.getDimension(R.styleable.LoopRotarySwitchView_r, LoopR);
124 | int direction = typedArray.getInt(R.styleable.LoopRotarySwitchView_direction, 0);
125 | typedArray.recycle();
126 | mGestureDetector = new GestureDetector(context, getGeomeryController());
127 | if (mOrientation == horizontal) {//如果是水平 z值为0 如果是竖直z值为90
128 | loopRotationZ = 0;
129 | } else {
130 | loopRotationZ = 90;
131 | }
132 | if (direction == 0) {//设置自定滚动的方向
133 | autoRotatinDirection = AutoScrollDirection.left;
134 | } else {
135 | autoRotatinDirection = AutoScrollDirection.right;
136 | }
137 | loopHandler.setLoop(autoRotation);
138 |
139 | }
140 |
141 | /**
142 | * handler处理
143 | */
144 | @SuppressLint("HandlerLeak")
145 | LoopRotarySwitchViewHandler loopHandler = new LoopRotarySwitchViewHandler(3000) {
146 | @Override
147 | public void doScroll() {
148 | try {
149 | if (size != 0) {//判断自动滑动从那边开始
150 | int perAngle = 0;
151 | switch (autoRotatinDirection) {
152 | case left:
153 | perAngle = 360 / size;
154 | break;
155 | case right:
156 | perAngle = -360 / size;
157 | break;
158 | }
159 | if (angle == 360) {
160 | angle = 0f;
161 | }
162 | animRotationTo(angle + perAngle, null);
163 | }
164 | } catch (Exception e) {
165 | e.printStackTrace();
166 | }
167 | }
168 | };
169 |
170 | /**
171 | * 排序
172 | * 对子View 排序,然后根据变化选中是否重绘,这样是为了实现view 在显示的时候来控制当前要显示的是哪三个view,可以改变排序看下效果
173 | *
174 | * @param list
175 | */
176 | @SuppressWarnings("unchecked")
177 | private void sortList(List list) {
178 |
179 | @SuppressWarnings("rawtypes")
180 | Comparator comparator = new SortComparator();
181 | T[] array = list.toArray((T[]) new Object[list.size()]);
182 |
183 | Arrays.sort(array, comparator);
184 | int i = 0;
185 | ListIterator it = (ListIterator) list.listIterator();
186 | while (it.hasNext()) {
187 | it.next();
188 | it.set(array[i++]);
189 | }
190 | for (int j = 0; j < list.size(); j++) {
191 | list.get(j).bringToFront();
192 | }
193 | }
194 |
195 | /**
196 | * 筛选器
197 | */
198 | private class SortComparator implements Comparator {
199 | @Override
200 | public int compare(View lhs, View rhs) {
201 | int result = 0;
202 | try {
203 | result = (int) (1000 * lhs.getScaleX() - 1000 * rhs.getScaleX());
204 | } catch (Exception e) {
205 | }
206 | return result;
207 | }
208 | }
209 |
210 | /**
211 | * 手势
212 | *
213 | * @return
214 | */
215 | private GestureDetector.SimpleOnGestureListener getGeomeryController() {
216 | return new GestureDetector.SimpleOnGestureListener() {
217 | @Override
218 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
219 | angle += Math.cos(Math.toRadians(loopRotationZ)) * (distanceX / 4)
220 | + Math.sin(Math.toRadians(loopRotationZ)) * (distanceY / 4);
221 | initView();
222 | return true;
223 | }
224 | };
225 | }
226 |
227 | public void initView() {
228 | for (int i = 0; i < views.size(); i++) {
229 | double radians = angle + 180 - i * 360 / size;
230 | float x0 = (float) Math.sin(Math.toRadians(radians)) * r;
231 | float y0 = (float) Math.cos(Math.toRadians(radians)) * r;
232 | float scale0 = (distance - y0) / (distance + r);//计算子view之间的比例,可以看到distance越大的话 比例越小,也就是大小就相差越小
233 | views.get(i).setScaleX(scale0);//对view进行缩放
234 | views.get(i).setScaleY(scale0);//对view进行缩放
235 | float rotationX_y = (float) Math.sin(Math.toRadians(loopRotationX * Math.cos(Math.toRadians(radians)))) * r;
236 | float rotationZ_y = -(float) Math.sin(Math.toRadians(-loopRotationZ)) * x0;
237 | float rotationZ_x = (((float) Math.cos(Math.toRadians(-loopRotationZ)) * x0) - x0);
238 | views.get(i).setTranslationX(x0 + rotationZ_x);
239 | views.get(i).setTranslationY(rotationX_y + rotationZ_y);
240 | }
241 | List arrayViewList = new ArrayList<>();
242 | arrayViewList.clear();
243 | for (int i = 0; i < views.size(); i++) {
244 | arrayViewList.add(views.get(i));
245 | }
246 | sortList(arrayViewList);
247 | postInvalidate();
248 | }
249 |
250 | @Override
251 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
252 | super.onSizeChanged(w, h, oldw, oldh);
253 | initView();
254 | if (autoRotation) {
255 | loopHandler.sendEmptyMessageDelayed(LoopRotarySwitchViewHandler.msgid, loopHandler.loopTime);
256 | }
257 | }
258 |
259 | @Override
260 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
261 | super.onLayout(changed, l, t, r, b);
262 | if (changed) {
263 | checkChildView();
264 | if (onItemSelectedListener != null) {
265 | isCanClickListener = true;
266 | onItemSelectedListener.selected(selectItem, views.get(selectItem));
267 | }
268 | rAnimation();
269 | }
270 | }
271 |
272 | public void rAnimation() {
273 | rAnimation(1f, r);
274 | }
275 |
276 | public void rAnimation(boolean fromZeroToLoopR) {
277 | if (fromZeroToLoopR) {
278 | rAnimation(1f, LoopR);
279 | } else {
280 | rAnimation(LoopR, 1f);
281 | }
282 | }
283 |
284 | public void rAnimation(float from, float to) {
285 | rAnimation = ValueAnimator.ofFloat(from, to);
286 | rAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
287 | @Override
288 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
289 | r = (Float) valueAnimator.getAnimatedValue();
290 | initView();
291 | }
292 | });
293 | rAnimation.setInterpolator(new DecelerateInterpolator());
294 | rAnimation.setDuration(2000);
295 | rAnimation.start();
296 | }
297 |
298 |
299 | /**
300 | * 初始化view
301 | */
302 | public void checkChildView() {
303 | for (int i = 0; i < views.size(); i++) {//先清空views里边可能存在的view防止重复
304 | views.remove(i);
305 | }
306 | final int count = getChildCount(); //获取子View的个数
307 | size = count;
308 |
309 | for (int i = 0; i < count; i++) {
310 | View view = getChildAt(i); //获取指定的子view
311 | final int position = i;
312 | views.add(view);
313 | view.setOnClickListener(new OnClickListener() {
314 | @Override
315 | public void onClick(View v) {
316 | //对子view添加点击事件
317 | if (position != selectItem) {
318 | setSelectItem(position);
319 | } else {
320 | if (isCanClickListener && onItemClickListener != null) {
321 | onItemClickListener.onItemClick(position, views.get(position));
322 | }
323 | }
324 | }
325 | });
326 |
327 | }
328 |
329 | }
330 |
331 | /**
332 | * 复位
333 | */
334 | private void restPosition() {
335 | if (size == 0) {
336 | return;
337 | }
338 | float finall = 0;
339 | float part = 360 / size;//一份的角度
340 | if (angle < 0) {
341 | part = -part;
342 | }
343 | float minvalue = (int) (angle / part) * part;//最小角度
344 | float maxvalue = (int) (angle / part) * part + part;//最大角度
345 | if (angle >= 0) {//分为是否小于0的情况
346 | if (angle - last_angle > 0) {
347 | finall = maxvalue;
348 | } else {
349 | finall = minvalue;
350 | }
351 | } else {
352 | if (angle - last_angle < 0) {
353 | finall = maxvalue;
354 | } else {
355 | finall = minvalue;
356 | }
357 | }
358 | animRotationTo(finall, null);
359 | }
360 |
361 |
362 | /**
363 | * 动画
364 | *
365 | * @param finall
366 | * @param complete
367 | */
368 | private void animRotationTo(float finall, final Runnable complete) {
369 | if (angle == finall) {//如果相同说明不需要旋转
370 | return;
371 | }
372 | restAnimator = ValueAnimator.ofFloat(angle, finall);
373 | restAnimator.setInterpolator(new DecelerateInterpolator());//设置旋转减速插值器
374 | restAnimator.setDuration(300);
375 |
376 | restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
377 | @Override
378 | public void onAnimationUpdate(ValueAnimator animation) {
379 | if (touching == false) {
380 | angle = (Float) animation.getAnimatedValue();
381 | initView();
382 | }
383 | }
384 | });
385 | restAnimator.addListener(new Animator.AnimatorListener() {
386 | @Override
387 | public void onAnimationStart(Animator animation) {
388 |
389 | }
390 |
391 | @Override
392 | public void onAnimationEnd(Animator animation) {
393 | if (touching == false) {
394 | selectItem = calculateItem();
395 | if (selectItem < 0) {
396 | selectItem = size + selectItem;
397 | }
398 | if (onItemSelectedListener != null) {
399 | onItemSelectedListener.selected(selectItem, views.get(selectItem));
400 | }
401 | }
402 | }
403 |
404 | @Override
405 | public void onAnimationCancel(Animator animation) {
406 |
407 | }
408 |
409 | @Override
410 | public void onAnimationRepeat(Animator animation) {
411 |
412 | }
413 | });
414 |
415 | if (complete != null) {
416 | restAnimator.addListener(new Animator.AnimatorListener() {
417 | @Override
418 | public void onAnimationStart(Animator animation) {
419 |
420 | }
421 |
422 | @Override
423 | public void onAnimationEnd(Animator animation) {
424 | complete.run();
425 | }
426 |
427 | @Override
428 | public void onAnimationCancel(Animator animation) {
429 |
430 | }
431 |
432 | @Override
433 | public void onAnimationRepeat(Animator animation) {
434 |
435 | }
436 | });
437 | }
438 | restAnimator.start();
439 | }
440 |
441 | /**
442 | * 通过角度计算是第几个item
443 | *
444 | * @return
445 | */
446 | private int calculateItem() {
447 | return (int) (angle / (360 / size)) % size;
448 | }
449 |
450 | /**
451 | * 触摸操作
452 | *
453 | * @param event
454 | * @return
455 | */
456 | private boolean onTouch(MotionEvent event) {
457 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
458 | last_angle = angle;
459 | touching = true;
460 | }
461 | boolean sc = mGestureDetector.onTouchEvent(event);
462 | if (sc) {
463 | this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件
464 | }
465 | if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
466 | touching = false;
467 | restPosition();
468 | return true;
469 | }
470 | return true;
471 | }
472 |
473 |
474 | /**
475 | * 触摸方法
476 | *
477 | * @param event
478 | * @return
479 | */
480 | @Override
481 | public boolean onTouchEvent(MotionEvent event) {
482 | if (onLoopViewTouchListener != null) {
483 | onLoopViewTouchListener.onTouch(event);
484 | }
485 | isCanClickListener(event);
486 | return true;
487 | }
488 |
489 |
490 | /**
491 | * 触摸停止计时器,抬起设置可下啦刷新
492 | */
493 | @Override
494 | public boolean dispatchTouchEvent(MotionEvent ev) {
495 | onTouch(ev);
496 | if (onLoopViewTouchListener != null) {
497 | onLoopViewTouchListener.onTouch(ev);
498 | }
499 | isCanClickListener(ev);
500 | return super.dispatchTouchEvent(ev);
501 | }
502 |
503 | /**
504 | * 是否可以点击回调
505 | *
506 | * @param event
507 | */
508 | public void isCanClickListener(MotionEvent event) {
509 | switch (event.getAction()) {
510 | case MotionEvent.ACTION_DOWN:
511 | x = event.getX();
512 | if (autoRotation) {
513 | loopHandler.removeMessages(LoopRotarySwitchViewHandler.msgid);
514 | }
515 | break;
516 | case MotionEvent.ACTION_MOVE:
517 | break;
518 | case MotionEvent.ACTION_UP:
519 | case MotionEvent.ACTION_CANCEL:
520 | if (autoRotation) {
521 | loopHandler.sendEmptyMessageDelayed(LoopRotarySwitchViewHandler.msgid, loopHandler.loopTime);
522 | }
523 | if (event.getX() - x > limitX || x - event.getX() > limitX) {
524 | isCanClickListener = false;
525 | } else {
526 | isCanClickListener = true;
527 | }
528 | break;
529 | }
530 | }
531 |
532 | /**
533 | * 获取所有的view
534 | *
535 | * @return
536 | */
537 | public List getViews() {
538 | return views;
539 | }
540 |
541 | /**
542 | * 获取角度
543 | *
544 | * @return
545 | */
546 | public float getAngle() {
547 | return angle;
548 | }
549 |
550 |
551 | /**
552 | * 设置角度
553 | *
554 | * @param angle
555 | */
556 | public void setAngle(float angle) {
557 | this.angle = angle;
558 | }
559 |
560 | /**
561 | * 获取距离
562 | *
563 | * @return
564 | */
565 | public float getDistance() {
566 | return distance;
567 | }
568 |
569 | /**
570 | * 设置距离
571 | *
572 | * @param distance
573 | */
574 | public void setDistance(float distance) {
575 | this.distance = distance;
576 | }
577 |
578 | /**
579 | * 获取半径
580 | *
581 | * @return
582 | */
583 | public float getR() {
584 | return r;
585 | }
586 |
587 | /**
588 | * 获取选择是第几个item
589 | *
590 | * @return
591 | */
592 | public int getSelectItem() {
593 | return selectItem;
594 | }
595 |
596 | /**
597 | * 设置选中方法
598 | *
599 | * @param selectItem
600 | */
601 | public void setSelectItem(int selectItem) {
602 |
603 | if (selectItem >= 0) {
604 | float jiaodu = 0;
605 | if (getSelectItem() == 0) {
606 | if (selectItem == views.size() - 1) {
607 | jiaodu = angle - (360 / size);
608 | } else {
609 | jiaodu = angle + (360 / size);
610 | }
611 | } else if (getSelectItem() == views.size() - 1) {
612 | if (selectItem == 0) {
613 | jiaodu = angle + (360 / size);
614 | } else {
615 | jiaodu = angle - (360 / size);
616 | }
617 | } else {
618 | if (selectItem > getSelectItem()) {
619 | jiaodu = angle + (360 / size);
620 | } else {
621 | jiaodu = angle - (360 / size);
622 | }
623 | }
624 |
625 | float finall = 0;
626 | float part = 360 / size;//一份的角度
627 | if (jiaodu < 0) {
628 | part = -part;
629 | }
630 | float minvalue = (int) (jiaodu / part) * part;//最小角度
631 | float maxvalue = (int) (jiaodu / part) * part;//最大角度
632 | if (jiaodu >= 0) {//分为是否小于0的情况
633 | if (jiaodu - last_angle > 0) {
634 | finall = maxvalue;
635 | } else {
636 | finall = minvalue;
637 | }
638 | } else {
639 | if (jiaodu - last_angle < 0) {
640 | finall = maxvalue;
641 | } else {
642 | finall = minvalue;
643 | }
644 | }
645 |
646 | if (size > 0) animRotationTo(finall, null);
647 | }
648 | }
649 |
650 | /**
651 | * 设置半径
652 | *
653 | * @param r
654 | */
655 | public LoopRotarySwitchView setR(float r) {
656 | this.r = r;
657 | distance = multiple * r;
658 | return this;
659 | }
660 |
661 | /**
662 | * 选中回调接口实现
663 | *
664 | * @param onItemSelectedListener
665 | */
666 | public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
667 | this.onItemSelectedListener = onItemSelectedListener;
668 | }
669 |
670 | /**
671 | * 点击事件回调
672 | *
673 | * @param onItemClickListener
674 | */
675 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
676 | this.onItemClickListener = onItemClickListener;
677 | }
678 |
679 | /**
680 | * 触摸时间回调
681 | *
682 | * @param onLoopViewTouchListener
683 | */
684 | public void setOnLoopViewTouchListener(OnLoopViewTouchListener onLoopViewTouchListener) {
685 | this.onLoopViewTouchListener = onLoopViewTouchListener;
686 | }
687 |
688 | /**
689 | * 设置是否自动切换
690 | *
691 | * @param autoRotation
692 | */
693 | public LoopRotarySwitchView setAutoRotation(boolean autoRotation) {
694 | this.autoRotation = autoRotation;
695 | loopHandler.setLoop(autoRotation);
696 | return this;
697 | }
698 |
699 | /**
700 | * 获取自动切换时间
701 | *
702 | * @return
703 | */
704 | public long getAutoRotationTime() {
705 | return loopHandler.loopTime;
706 | }
707 |
708 | /**
709 | * 设置自动切换时间间隔
710 | *
711 | * @param autoRotationTime
712 | */
713 | public LoopRotarySwitchView setAutoRotationTime(long autoRotationTime) {
714 | loopHandler.setLoopTime(autoRotationTime);
715 | return this;
716 | }
717 |
718 | /**
719 | * 是否自动切换
720 | *
721 | * @return
722 | */
723 | public boolean isAutoRotation() {
724 | return autoRotation;
725 | }
726 |
727 | /**
728 | * 设置倍数
729 | *
730 | * @param mMultiple 设置这个必须在setR之前调用,否则无效
731 | * @return
732 | */
733 | public LoopRotarySwitchView setMultiple(float mMultiple) {
734 | this.multiple = mMultiple;
735 | return this;
736 | }
737 |
738 | public LoopRotarySwitchView setAutoScrollDirection(AutoScrollDirection mAutoScrollDirection) {
739 | this.autoRotatinDirection = mAutoScrollDirection;
740 | return this;
741 | }
742 |
743 | public void createXAnimation(int from, int to, boolean start) {
744 | if (xAnimation != null) if (xAnimation.isRunning() == true) xAnimation.cancel();
745 | xAnimation = ValueAnimator.ofInt(from, to);
746 | xAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
747 | @Override
748 | public void onAnimationUpdate(ValueAnimator animation) {
749 | loopRotationX = (Integer) animation.getAnimatedValue();
750 | initView();
751 | }
752 | });
753 | xAnimation.setInterpolator(new DecelerateInterpolator());
754 | xAnimation.setDuration(2000);
755 | if (start) xAnimation.start();
756 | }
757 |
758 |
759 | public ValueAnimator createZAnimation(int from, int to, boolean start) {
760 | if (zAnimation != null) if (zAnimation.isRunning() == true) zAnimation.cancel();
761 | zAnimation = ValueAnimator.ofInt(from, to);
762 | zAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
763 | @Override
764 | public void onAnimationUpdate(ValueAnimator animation) {
765 | loopRotationZ = (Integer) animation.getAnimatedValue();
766 | initView();
767 | }
768 | });
769 | zAnimation.setInterpolator(new DecelerateInterpolator());
770 | zAnimation.setDuration(2000);
771 | if (start) zAnimation.start();
772 | return zAnimation;
773 | }
774 |
775 | /**
776 | * 设置方向
777 | *
778 | * @param mOrientation
779 | * @return
780 | */
781 | public LoopRotarySwitchView setOrientation(int mOrientation) {
782 | setHorizontal(mOrientation == horizontal, false);
783 | return this;
784 | }
785 |
786 | public LoopRotarySwitchView setHorizontal(boolean horizontal, boolean anim) {
787 | if (anim) {
788 | if (horizontal) {
789 | createZAnimation(getLoopRotationZ(), 0, true);
790 | } else {
791 | createZAnimation(getLoopRotationZ(), 90, true);
792 | }
793 | } else {
794 | if (horizontal) {
795 | setLoopRotationZ(0);
796 | } else {
797 | setLoopRotationZ(90);
798 | }
799 | initView();
800 | }
801 | return this;
802 | }
803 |
804 | public LoopRotarySwitchView setLoopRotationX(int loopRotationX) {
805 | this.loopRotationX = loopRotationX;
806 | return this;
807 | }
808 |
809 | public LoopRotarySwitchView setLoopRotationZ(int loopRotationZ) {
810 | this.loopRotationZ = loopRotationZ;
811 | return this;
812 | }
813 |
814 | public int getLoopRotationX() {
815 | return loopRotationX;
816 | }
817 |
818 | public int getLoopRotationZ() {
819 | return loopRotationZ;
820 | }
821 |
822 | public ValueAnimator getRestAnimator() {
823 | return restAnimator;
824 | }
825 |
826 | public ValueAnimator getrAnimation() {
827 | return rAnimation;
828 | }
829 |
830 | public void setzAnimation(ValueAnimator zAnimation) {
831 | this.zAnimation = zAnimation;
832 | }
833 |
834 | public ValueAnimator getzAnimation() {
835 | return zAnimation;
836 | }
837 |
838 | public void setxAnimation(ValueAnimator xAnimation) {
839 | this.xAnimation = xAnimation;
840 | }
841 |
842 | public ValueAnimator getxAnimation() {
843 | return xAnimation;
844 | }
845 | }
846 |
--------------------------------------------------------------------------------
/loopplay/src/main/java/com/slidemove/library/view/LoopRotarySwitchViewHandler.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library.view;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | /**
7 | * loopHandler 发送
8 | */
9 | public abstract class LoopRotarySwitchViewHandler extends Handler {
10 |
11 | private boolean loop = false;//是否要发送
12 |
13 | public long loopTime = 3000;//时间间隔
14 |
15 | public static final int msgid = 1000;//id
16 |
17 | private Message msg = createMsg();//创建message
18 |
19 | /**
20 | * 构造方法
21 | *
22 | * @param time
23 | */
24 | public LoopRotarySwitchViewHandler(int time) {
25 | this.loopTime = time;
26 | }
27 |
28 | /**
29 | * handler处理
30 | *
31 | * @param msg
32 | */
33 | @Override
34 | public void handleMessage(Message msg) {
35 | switch (msg.what = msgid) {
36 | case msgid:
37 | if (loop) {
38 | doScroll();
39 | sendMsg();
40 | }
41 | break;
42 | }
43 | super.handleMessage(msg);
44 | }
45 |
46 | /**
47 | * 设置是否要发送
48 | *
49 | * @param loop
50 | */
51 | public void setLoop(boolean loop) {
52 | this.loop = loop;
53 | if (loop) {
54 | sendMsg();
55 | } else {
56 | try {
57 | removeMessages(msgid);
58 | } catch (Exception e) {
59 | }
60 | }
61 | }
62 |
63 | /**
64 | * 发送message
65 | */
66 | private void sendMsg() {
67 | try {
68 | removeMessages(msgid);
69 | } catch (Exception e) {
70 | }
71 | msg = createMsg();
72 | this.sendMessageDelayed(msg, loopTime);
73 | }
74 |
75 | /**
76 | * 创建message
77 | *
78 | * @return
79 | */
80 | public Message createMsg() {
81 | Message msg = new Message();
82 | msg.what = msgid;
83 | return msg;
84 | }
85 |
86 | /**
87 | * 设置时间
88 | *
89 | * @param loopTime
90 | */
91 | public void setLoopTime(long loopTime) {
92 | this.loopTime = loopTime;
93 | }
94 |
95 | public long getLoopTime() {
96 | return loopTime;
97 | }
98 |
99 | /**
100 | * 返回是否可以发送
101 | *
102 | * @return
103 | */
104 | public boolean isLoop() {
105 | return loop;
106 | }
107 |
108 | public abstract void doScroll();
109 | }
110 |
--------------------------------------------------------------------------------
/loopplay/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/loopplay/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LoopViewLibrary
3 |
4 |
--------------------------------------------------------------------------------
/loopplay/src/test/java/com/slidemove/library/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.slidemove.library;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':loopplay'
2 |
--------------------------------------------------------------------------------