├── .gitignore ├── README.md ├── build.gradle ├── demo.gif ├── example ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cry │ │ └── animation │ │ └── activity │ │ └── MainActivity.java │ └── res │ ├── drawable │ ├── drawable_point0.xml │ ├── drawable_point1.xml │ ├── drawable_point2.xml │ ├── drawable_point_item.xml │ ├── ic_launcher.png │ └── image_shader.png │ ├── layout │ ├── activity_main.xml │ ├── item_pager.xml │ ├── item_textv.xml │ ├── item_view0.xml │ ├── item_view1.xml │ └── item_view2.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hao.png ├── loopview-1.4.5.jar ├── loopviews ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── cry │ └── loopviews │ ├── LoopHandler.java │ ├── LoopView.java │ ├── LoopViewPager.java │ └── listener │ ├── OnInvateListener.java │ └── OnItemSelectedListener.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | # Files for the Dalvik VM 5 | *.dex 6 | # Java class files 7 | *.class 8 | # Generated files 9 | bin/ 10 | gen/ 11 | build/ 12 | # Gradle files 13 | .gradle/ 14 | /*/build/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Proguard folder generated by Eclipse 20 | proguard/ 21 | 22 | # Log Files 23 | *.log 24 | 25 | #idea 26 | *.idea 27 | *.iml 28 | local.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #介绍 2 | #### LoopViewPager 继承自FrameLayout,不仅支持无限循环,相比android.support.v4中的ViewPager,更加简单易用。PS目前很多人实现是用int最大值的办法,但是数量必须在3个以上,不好.Loop是底层实现,仅仅缓存一个view
3 | #### LoopView 继承自RelativeLayout,不仅支持无限循环,而且有许多酷炫的动画和自定义功能,支持x轴,z轴,半径的动态 改变外观。非常易于使用。
4 | 5 | ![image](demo.gif) 6 | 7 | ## 优势 8 | *3d旋转GrallyView,继承FrameLayout
9 | *LoopViewPager不仅支持无限循环,还支持左右和上下切换,同时支持Handler实现的自动切换 10 | *支持使用Handler实现的自动旋转,相比使用线程更加节省性能
11 | *可直接在xml添加元素即可添加列数据。无需编写代码添加
12 | *优良的兼容性,和尺寸控制
13 | ## 如何使用? 14 | ### Step1:导入jar文件加入到项目 15 | ## [点击下载最新版本jar文件 jar file](https://github.com/zhuxiujia/LoopView/blob/master/loopview-1.4.5.jar?raw=true)
16 | ### Step2:在你的layout目录下编写加入你的xml文件 17 | ``` 18 | 23 | 24 | 29 | 30 | ``` 31 | ## 问题反馈和联系方式 32 | qq:347284221
33 | 邮箱:zhuxiujia@qq.com
34 | ## 原理示意图 35 | ![ABC](hao.png) 36 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/demo.gif -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.0" 6 | 7 | defaultConfig { 8 | applicationId "com.cry.animation" 9 | minSdkVersion 14 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:24.0.0' 25 | compile project(':loopviews') 26 | } 27 | -------------------------------------------------------------------------------- /example/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/zxj/Desktop/adt-bundle-mac-x86_64-20140702/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/src/main/java/com/cry/animation/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cry.animation.activity; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.CheckBox; 9 | import android.widget.CompoundButton; 10 | import android.widget.SeekBar; 11 | import android.widget.Switch; 12 | import android.widget.TextView; 13 | 14 | import com.cry.animation.R; 15 | import com.cry.loopviews.LoopView; 16 | import com.cry.loopviews.LoopViewPager; 17 | import com.cry.loopviews.listener.OnInvateListener; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | 23 | public class MainActivity extends Activity { 24 | LoopView loopView; 25 | LoopViewPager loopViewPager; 26 | 27 | SeekBar seekBar_x, seekBar_z; 28 | 29 | CheckBox 30 | checkbox_zd_loopviewpager, 31 | checkbox_zd_loopview, 32 | checkbox_use_textview; 33 | 34 | Switch switch_r_animation, 35 | switch_hx, 36 | switch_hx_loopview; 37 | TextView textView_info; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | 44 | initAllViews(); 45 | initLoopView(); 46 | initListener(); 47 | } 48 | 49 | private void initAllViews() { 50 | textView_info = (TextView) findViewById(R.id.textView_info); 51 | 52 | switch_hx = (Switch) findViewById(R.id.switch_hx); 53 | checkbox_zd_loopviewpager = (CheckBox) findViewById(R.id.checkbox_zd_loopviewpager); 54 | checkbox_zd_loopview = (CheckBox) findViewById(R.id.checkbox_zd_loopview); 55 | switch_hx_loopview = (Switch) findViewById(R.id.switch_hx_loopview); 56 | switch_r_animation = (Switch) findViewById(R.id.switch_r_animation); 57 | checkbox_use_textview = (CheckBox) findViewById(R.id.checkbox_use_textview); 58 | 59 | 60 | seekBar_x = (SeekBar) findViewById(R.id.seekBar_x); 61 | seekBar_z = (SeekBar) findViewById(R.id.seekBar_z); 62 | 63 | seekBar_x.setProgress(seekBar_x.getMax() / 2); 64 | seekBar_z.setProgress(seekBar_z.getMax() / 2); 65 | 66 | //LoopViewPager 使用方法--------------------------------------------- 67 | loopViewPager = (LoopViewPager) findViewById(R.id.loopViewPager); 68 | loopViewPager.setAutoChangeTime(1 * 1000);//设置自动切换时间 69 | loopViewPager.setViewList(getViewList());//设置ViewList 70 | 71 | //LoopView 使用方法--------------------------------------------- 72 | loopView = (LoopView) findViewById(R.id.loopView0); 73 | } 74 | 75 | private void initListener() { 76 | 77 | loopView.setOnInvateListener(new OnInvateListener() { 78 | @Override 79 | public void onInvate(LoopView loopView) { 80 | try { 81 | //打印数据 82 | textView_info.setText( 83 | "\nRotation:" + (int) loopView.getAngle() 84 | + "\nRotationX:" + (int) loopView.getLoopRotationX() 85 | + "\nRotationZ:" + (int) loopView.getLoopRotationZ() 86 | + "\nR:" + loopView.getR() 87 | ); 88 | } catch (Exception e) { 89 | e.printStackTrace(); 90 | } 91 | } 92 | }); 93 | 94 | seekBar_x.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 95 | @Override 96 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 97 | loopView.setLoopRotationX(progress - seekBar.getMax() / 2); 98 | loopView.invate(); 99 | } 100 | 101 | @Override 102 | public void onStartTrackingTouch(SeekBar seekBar) { 103 | } 104 | 105 | @Override 106 | public void onStopTrackingTouch(SeekBar seekBar) { 107 | } 108 | }); 109 | seekBar_z.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 110 | @Override 111 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 112 | loopView.setLoopRotationZ(progress - seekBar.getMax() / 2); 113 | loopView.invate(); 114 | } 115 | 116 | @Override 117 | public void onStartTrackingTouch(SeekBar seekBar) { 118 | 119 | } 120 | 121 | @Override 122 | public void onStopTrackingTouch(SeekBar seekBar) { 123 | } 124 | }); 125 | switch_hx.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 126 | @Override 127 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 128 | loopViewPager.setHorizontal(isChecked);//设置LoopViewPager是否横向切换 129 | } 130 | }); 131 | switch_hx_loopview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 132 | @Override 133 | public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) { 134 | loopView.setHorizontal(isChecked, true);//设置LoopView是否横向切换 135 | } 136 | }); 137 | checkbox_zd_loopviewpager.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 138 | @Override 139 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 140 | loopViewPager.setAutoChange(isChecked);//启动LoopViewPager自动切换 141 | } 142 | }); 143 | checkbox_zd_loopview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 144 | @Override 145 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 146 | loopView.setAutoRotation(isChecked);//启动LoopView自动切换 147 | } 148 | }); 149 | switch_r_animation.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 150 | @Override 151 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 152 | loopView.RAnimation(isChecked);//半径动画 153 | } 154 | }); 155 | checkbox_use_textview.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 156 | @Override 157 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 158 | if (!isChecked) { 159 | loopView.removeAllViews(); 160 | loopView.addView(inflate(R.layout.item_view0)); 161 | loopView.addView(inflate(R.layout.item_view1)); 162 | loopView.addView(inflate(R.layout.item_view2)); 163 | buttonView.setHint("使用layout"); 164 | } else { 165 | loopView.removeAllViews(); 166 | for (int i = 0; i < 6; i++) { 167 | loopView.addView(inflate(R.layout.item_textv)); 168 | } 169 | buttonView.setHint("使用TextView"); 170 | } 171 | try { 172 | loopView.setSelectItem(0); 173 | } catch (Exception e) { 174 | e.printStackTrace(); 175 | } 176 | //loopView.RAnimation(true); 177 | } 178 | }); 179 | } 180 | 181 | private View inflate(int layout) { 182 | View v = LayoutInflater.from(MainActivity.this).inflate(layout, null); 183 | LoopView.LayoutParams params = new LoopView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 184 | params.addRule(LoopView.CENTER_IN_PARENT); 185 | v.setLayoutParams(params); 186 | return v; 187 | } 188 | 189 | 190 | private void initLoopView() { 191 | loopView.setAutoRotationTime(1 * 1000)//设置自动旋转时间 192 | .setR(getResources().getDimension(R.dimen.loopview_width) / 2)//设置半径 193 | .setLoopRotationX(0)//x轴旋转 194 | .setLoopRotationZ(0); //z轴旋转 195 | //.RAnimation(1f,loopView.getR());//半径动画 196 | } 197 | 198 | /*准备给ViewPager 设置要您的需要添加的View*/ 199 | private List getViewList() { 200 | List arr = new ArrayList(); 201 | for (int i = 0; i < 3; i++) { 202 | View v = LayoutInflater.from(this).inflate(R.layout.item_pager, null); 203 | TextView textView = (TextView) v.findViewById(R.id.textView); 204 | textView.setText("index" + i); 205 | arr.add(v); 206 | } 207 | return arr; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/drawable_point0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/drawable_point1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/drawable_point2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/drawable_point_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /example/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/example/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/drawable/image_shader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/example/src/main/res/drawable/image_shader.png -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 17 | 18 | 25 | 26 | 33 | 34 | 35 | 41 | 42 | 47 | 48 | 49 | 53 | 54 | 60 | 61 | 68 | 69 | 76 | 77 | 78 | 81 | 82 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 100 | 101 | 106 | 107 | 112 | 113 | 118 | 119 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_pager.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_textv.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_view0.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | 18 | 25 | 26 | 27 | 34 | 35 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_view1.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 18 | 19 | 26 | 27 | 28 | 35 | 36 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_view2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 18 | 19 | 26 | 27 | 28 | 35 | 36 | -------------------------------------------------------------------------------- /example/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /example/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/example/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/example/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/example/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /example/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 160dp 6 | 7 | -------------------------------------------------------------------------------- /example/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LoopViewDemo 3 | Hello world! 4 | Settings 5 | 6 | -------------------------------------------------------------------------------- /example/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 19 00:28:00 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /hao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/hao.png -------------------------------------------------------------------------------- /loopview-1.4.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuxiujia/LoopView/840b3d4818f7abcfbc538d0cb8641c55ba7648ec/loopview-1.4.5.jar -------------------------------------------------------------------------------- /loopviews/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /loopviews/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '24.0.0' 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | } 24 | -------------------------------------------------------------------------------- /loopviews/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/zxj/Desktop/adt-bundle-mac-x86_64-20140702/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 | -------------------------------------------------------------------------------- /loopviews/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /loopviews/src/main/java/com/cry/loopviews/LoopHandler.java: -------------------------------------------------------------------------------- 1 | package com.cry.loopviews; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | 6 | /** 7 | * Created by zxj on 15/8/14. 8 | */ 9 | public abstract class LoopHandler extends Handler { 10 | static final int msgid = 1000; 11 | boolean loop = false; 12 | long loopTime = 0; 13 | Message msg = createMsg(); 14 | 15 | 16 | public LoopHandler(int time) { 17 | this.loopTime = time; 18 | } 19 | 20 | @Override 21 | public void handleMessage(Message msg) { 22 | switch (msg.what = msgid) { 23 | case msgid: 24 | if (loop) { 25 | du(); 26 | sendMsg(); 27 | } 28 | break; 29 | } 30 | super.handleMessage(msg); 31 | } 32 | 33 | private void sendMsg() { 34 | try { 35 | removeMessages(msgid); 36 | } catch (Exception e) { 37 | } 38 | msg = createMsg(); 39 | this.sendMessageDelayed(msg, loopTime); 40 | } 41 | 42 | public Message createMsg() { 43 | Message msg = new Message(); 44 | msg.what = msgid; 45 | return msg; 46 | } 47 | 48 | public void setLoopTime(long loopTime) { 49 | this.loopTime = loopTime; 50 | } 51 | 52 | public boolean isLoop() { 53 | return loop; 54 | } 55 | 56 | public void setLoop(boolean loop) { 57 | this.loop = loop; 58 | if (loop) { 59 | sendMsg(); 60 | } else { 61 | try { 62 | removeMessages(msgid); 63 | } catch (Exception e) { 64 | } 65 | } 66 | } 67 | 68 | public abstract void du(); 69 | } 70 | -------------------------------------------------------------------------------- /loopviews/src/main/java/com/cry/loopviews/LoopView.java: -------------------------------------------------------------------------------- 1 | package com.cry.loopviews; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.GestureDetector; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | import android.view.animation.DecelerateInterpolator; 12 | import android.widget.RelativeLayout; 13 | 14 | import com.cry.loopviews.listener.OnInvateListener; 15 | import com.cry.loopviews.listener.OnItemSelectedListener; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.Comparator; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by zxj on 15/5/20. 24 | * 水平旋转轮播控件 25 | */ 26 | public class LoopView extends RelativeLayout { 27 | final static int LoopR = 200; 28 | final String TAG = getClass().getSimpleName(); 29 | ValueAnimator restAnimator = null;//回位动画 30 | ValueAnimator rAnimation = null;//半径动画 31 | ValueAnimator zAnimation = null; 32 | ValueAnimator xAnimation = null; 33 | GestureDetector mGestureDetector = null;//手势类 34 | Comparator comp = new SortComparator(); 35 | List viewSortArray = new ArrayList<>(); 36 | List views = new ArrayList<>();//子view引用列表 37 | private int selectItem = 0;//当前选择项 38 | private int size = 0;//个数 39 | private float r = LoopR;//半径 40 | private float distance = 3 * r;//camera和观察的旋转物体距离, 距离越长,最大物体和最小物体比例越不明显 41 | private float angle = 0;//角度 42 | private float last_angle = 0; 43 | private boolean autoRotation = false;//自动旋转 44 | private boolean touching = false;//正在触摸 45 | private int loopRotationX = 0, loopRotationZ = 0;//x轴旋转和轴旋转,y轴无效果 46 | private OnInvateListener onInvateListener = null;//刷新侦听 47 | private OnItemSelectedListener onItemSelectedListener = null;//选择事件接口 48 | LoopHandler loopHandler = new LoopHandler(3000) {//自动切换角度用handler 49 | @Override 50 | public void du() { 51 | try { 52 | AnimRotationTo(angle - 360 / size, null); 53 | } catch (Exception e) { 54 | } 55 | } 56 | }; 57 | 58 | public LoopView(Context context) { 59 | super(context); 60 | init(context); 61 | } 62 | 63 | public LoopView(Context context, AttributeSet attrs) { 64 | super(context, attrs); 65 | init(context); 66 | } 67 | 68 | public LoopView(Context context, AttributeSet attrs, int defStyleAttr) { 69 | super(context, attrs, defStyleAttr); 70 | init(context); 71 | } 72 | 73 | /*初始化view attached 后,算子view 数目*/ 74 | private void init(final Context context) { 75 | mGestureDetector = new GestureDetector(context, getGeomeryController()); 76 | setOnHierarchyChangeListener(new OnHierarchyChangeListener() { 77 | @Override 78 | public void onChildViewAdded(View parent, View child) { 79 | views.add(child); 80 | size = getChildCount(); 81 | } 82 | 83 | @Override 84 | public void onChildViewRemoved(View parent, View child) { 85 | views.remove(child); 86 | size = getChildCount(); 87 | } 88 | }); 89 | } 90 | 91 | /*排序以整理重叠顺序*/ 92 | private void sortList(List list) { 93 | Collections.sort(list, comp); 94 | for (int i = 0; i < list.size(); i++) { 95 | list.get(i).bringToFront(); 96 | list.get(i).setEnabled(i == (list.size() - 1) && angle % (360 / size) == 0 ? true : false); 97 | } 98 | } 99 | 100 | /*触摸算角度,然后刷新*/ 101 | private GestureDetector.SimpleOnGestureListener getGeomeryController() { 102 | return new GestureDetector.SimpleOnGestureListener() { 103 | @Override 104 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 105 | angle += Math.cos(Math.toRadians(loopRotationZ)) * (distanceX / 4) 106 | + Math.sin(Math.toRadians(loopRotationZ)) * (distanceY / 4); 107 | invate(); 108 | return true; 109 | } 110 | }; 111 | } 112 | 113 | /*刷新数据变化到界面*/ 114 | public void invate() { 115 | for (int i = 0; i < views.size(); i++) { 116 | /*计算角度平均分后各个顶点位置*/ 117 | double radians = angle + 180 - i * 360 / size; 118 | float x0 = (float) Math.sin(Math.toRadians(radians)) * r; 119 | float y0 = (float) Math.cos(Math.toRadians(radians)) * r; 120 | float scale0 = (distance - y0) / (distance + r); 121 | views.get(i).setScaleX(scale0); 122 | views.get(i).setScaleY(scale0); 123 | float rotationX_y = (float) Math.sin(Math.toRadians(loopRotationX * Math.cos(Math.toRadians(radians)))) * r; 124 | float rotationZ_y = -(float) Math.sin(Math.toRadians(-loopRotationZ)) * x0; 125 | float rotationZ_x = (((float) Math.cos(Math.toRadians(-loopRotationZ)) * x0) - x0); 126 | 127 | views.get(i).setTranslationX(x0 + rotationZ_x); 128 | views.get(i).setTranslationY(rotationX_y + rotationZ_y); 129 | } 130 | redoSortArray(); 131 | sortList(viewSortArray); 132 | invalidate(); 133 | if (this.onInvateListener != null) { 134 | loopHandler.post(new Runnable() { 135 | @Override 136 | public void run() { 137 | onInvateListener.onInvate(LoopView.this); 138 | } 139 | }); 140 | } 141 | } 142 | 143 | /*角度刷新侦听*/ 144 | public void setOnInvateListener(OnInvateListener onInvateListener) { 145 | this.onInvateListener = onInvateListener; 146 | } 147 | 148 | /*重新排序*/ 149 | private void redoSortArray() { 150 | viewSortArray.clear(); 151 | for (int i = 0; i < views.size(); i++) { 152 | viewSortArray.add(views.get(i)); 153 | } 154 | } 155 | 156 | @Override 157 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 158 | super.onSizeChanged(w, h, oldw, oldh); 159 | invate(); 160 | } 161 | 162 | @Override 163 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 164 | super.onLayout(changed, l, t, r, b); 165 | if (changed) { 166 | setSelectItem(0); 167 | RAnimation(); 168 | } 169 | } 170 | 171 | /*创建x轴动画*/ 172 | public void createXAnimation(int from, int to, boolean start) { 173 | if (xAnimation != null) if (xAnimation.isRunning() == true) xAnimation.cancel(); 174 | xAnimation = ValueAnimator.ofInt(from, to); 175 | xAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 176 | @Override 177 | public void onAnimationUpdate(ValueAnimator animation) { 178 | loopRotationX = (Integer) animation.getAnimatedValue(); 179 | invate(); 180 | } 181 | }); 182 | xAnimation.setInterpolator(new DecelerateInterpolator()); 183 | xAnimation.setDuration(2000); 184 | if (start) xAnimation.start(); 185 | } 186 | 187 | /*z轴动画*/ 188 | public ValueAnimator createZAnimation(int from, int to, boolean start) { 189 | if (zAnimation != null) if (zAnimation.isRunning() == true) zAnimation.cancel(); 190 | zAnimation = ValueAnimator.ofInt(from, to); 191 | zAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 192 | @Override 193 | public void onAnimationUpdate(ValueAnimator animation) { 194 | loopRotationZ = (Integer) animation.getAnimatedValue(); 195 | invate(); 196 | } 197 | }); 198 | zAnimation.setInterpolator(new DecelerateInterpolator()); 199 | zAnimation.setDuration(2000); 200 | if (start) zAnimation.start(); 201 | return zAnimation; 202 | } 203 | 204 | /*半径动画*/ 205 | public void RAnimation() { 206 | RAnimation(1f, r); 207 | } 208 | 209 | /*半径动画*/ 210 | public void RAnimation(boolean fromZeroToLoopR) { 211 | if (fromZeroToLoopR) { 212 | RAnimation(1f, LoopR); 213 | } else { 214 | RAnimation(LoopR, 1f); 215 | } 216 | } 217 | 218 | /*半径动画*/ 219 | public void RAnimation(float from, float to) { 220 | if (rAnimation != null) if (rAnimation.isRunning() == true) rAnimation.cancel(); 221 | rAnimation = ValueAnimator.ofFloat(from, to); 222 | rAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 223 | @Override 224 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 225 | r = (Float) valueAnimator.getAnimatedValue(); 226 | invate(); 227 | } 228 | }); 229 | rAnimation.setInterpolator(new DecelerateInterpolator()); 230 | rAnimation.setDuration(2000); 231 | rAnimation.start(); 232 | } 233 | 234 | /*重置位置*/ 235 | private void restPosition() { 236 | if (size == 0) { 237 | return; 238 | } 239 | float finall = 0; 240 | float part = 360 / size;//一份的角度 241 | if (angle < 0) { 242 | part = -part; 243 | } 244 | float minvalue = (int) (angle / part) * part;//最小角度 245 | float maxvalue = (int) (angle / part) * part + part;//最大角度 246 | if (angle >= 0) {//分为是否小于0的情况 247 | if (angle - last_angle > 0) { 248 | finall = maxvalue; 249 | } else { 250 | finall = minvalue; 251 | } 252 | } else { 253 | if (angle - last_angle < 0) { 254 | finall = maxvalue; 255 | } else { 256 | finall = minvalue; 257 | } 258 | } 259 | AnimRotationTo(finall, null); 260 | } 261 | 262 | /*播放旋转动画*/ 263 | private void AnimRotationTo(float finall, final Runnable complete) { 264 | if (angle == finall) { 265 | return; 266 | } 267 | restAnimator = ValueAnimator.ofFloat(angle, finall); 268 | restAnimator.setInterpolator(new DecelerateInterpolator()); 269 | restAnimator.setDuration(300); 270 | restAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 271 | @Override 272 | public void onAnimationUpdate(ValueAnimator animation) { 273 | if (touching == false) { 274 | angle = (Float) animation.getAnimatedValue(); 275 | invate(); 276 | } 277 | } 278 | }); 279 | restAnimator.addListener(new Animator.AnimatorListener() { 280 | @Override 281 | public void onAnimationStart(Animator animation) { 282 | 283 | } 284 | 285 | @Override 286 | public void onAnimationEnd(Animator animation) { 287 | if (touching == false) { 288 | selectItem = calculateItem(); 289 | if (selectItem < 0) { 290 | selectItem = size + selectItem; 291 | } 292 | if (onItemSelectedListener != null) { 293 | onItemSelectedListener.selected(selectItem, views.get(selectItem)); 294 | } 295 | } 296 | } 297 | 298 | @Override 299 | public void onAnimationCancel(Animator animation) { 300 | 301 | } 302 | 303 | @Override 304 | public void onAnimationRepeat(Animator animation) { 305 | 306 | } 307 | }); 308 | 309 | if (complete != null) { 310 | restAnimator.addListener(new Animator.AnimatorListener() { 311 | @Override 312 | public void onAnimationStart(Animator animation) { 313 | 314 | } 315 | 316 | @Override 317 | public void onAnimationEnd(Animator animation) { 318 | complete.run(); 319 | } 320 | 321 | @Override 322 | public void onAnimationCancel(Animator animation) { 323 | 324 | } 325 | 326 | @Override 327 | public void onAnimationRepeat(Animator animation) { 328 | 329 | } 330 | }); 331 | } 332 | restAnimator.start(); 333 | } 334 | 335 | /*计算item*/ 336 | private int calculateItem() { 337 | return (int) (angle / (360 / size)) % size; 338 | } 339 | 340 | private boolean onTouch(MotionEvent event) { 341 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 342 | last_angle = angle; 343 | touching = true; 344 | } 345 | boolean sc = mGestureDetector.onTouchEvent(event); 346 | if (sc) { 347 | this.getParent().requestDisallowInterceptTouchEvent(true);//通知父控件勿拦截本控件 348 | } 349 | if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { 350 | touching = false; 351 | restPosition(); 352 | return true; 353 | } 354 | return true; 355 | } 356 | 357 | @Override 358 | public boolean onTouchEvent(MotionEvent event) { 359 | return true; 360 | } 361 | 362 | @Override 363 | public boolean dispatchTouchEvent(MotionEvent ev) { 364 | onTouch(ev); 365 | return super.dispatchTouchEvent(ev); 366 | } 367 | 368 | public List getViews() { 369 | return views; 370 | } 371 | 372 | public float getAngle() { 373 | return angle; 374 | } 375 | 376 | public LoopView setAngle(float angle) { 377 | this.angle = angle; 378 | return this; 379 | } 380 | 381 | public float getDistance() { 382 | return distance; 383 | } 384 | 385 | public LoopView setDistance(float distance) { 386 | this.distance = distance; 387 | return this; 388 | } 389 | 390 | public float getR() { 391 | return r; 392 | } 393 | 394 | public LoopView setR(float r) { 395 | this.r = r; 396 | distance = 3 * r; 397 | return this; 398 | } 399 | 400 | public int getSelectItem() { 401 | return selectItem; 402 | } 403 | 404 | /*selecItem must > 0*/ 405 | public LoopView setSelectItem(int selectItem) { 406 | if (size > 0) { 407 | if (selectItem > 0 && selectItem < size) { 408 | this.selectItem = selectItem; 409 | AnimRotationTo(selectItem * (360 / size), null); 410 | } else { 411 | Log.e(TAG, "must selectItem >0 or selectItem { 503 | @Override 504 | public int compare(View lhs, View rhs) { 505 | int result = 0; 506 | try { 507 | result = (int) (1000 * lhs.getScaleX() - 1000 * rhs.getScaleX()); 508 | } catch (Exception e) { 509 | } 510 | return result; 511 | } 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /loopviews/src/main/java/com/cry/loopviews/LoopViewPager.java: -------------------------------------------------------------------------------- 1 | package com.cry.loopviews; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.util.AttributeSet; 7 | import android.view.GestureDetector; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | 13 | import com.cry.loopviews.listener.OnItemSelectedListener; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by zxj on 15/6/6. 20 | */ 21 | public class LoopViewPager extends FrameLayout { 22 | int pagerWidth = 0; 23 | int pagerHeight = 0; 24 | boolean touching = false;//is touching 25 | int last_touch_point = 0;//上次点击 26 | int position = 0;//now view x position 27 | private List viewList = new ArrayList<>();//view数组 28 | private GestureDetector mGestureDetector;//手势 29 | private int item = 0;//滑动项目 30 | private int distence = 0;//距离 31 | private ValueAnimator valueAnimator = null;//切换动画 32 | private boolean autoChange = false; 33 | private long autoChangeTime = 4000;//旋转时间 34 | private ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); 35 | private OnItemSelectedListener onItemSelectedListener = null; 36 | private boolean horizontal = true;// 37 | LoopHandler loopHandler = new LoopHandler(4000) { 38 | @Override 39 | public void du() { 40 | try { 41 | if (horizontal) { 42 | AnimationTo(getScrollX(), (getScrollX() / pagerWidth + 1) * pagerWidth); 43 | } else { 44 | AnimationTo(getScrollY(), (getScrollY() / pagerHeight + 1) * pagerHeight); 45 | } 46 | } catch (Exception e) { 47 | } 48 | } 49 | }; 50 | 51 | public LoopViewPager(Context context) { 52 | super(context); 53 | init(context); 54 | } 55 | 56 | public LoopViewPager(Context context, AttributeSet attrs) { 57 | super(context, attrs); 58 | init(context); 59 | } 60 | 61 | public LoopViewPager(Context context, AttributeSet attrs, int defStyleAttr) { 62 | super(context, attrs, defStyleAttr); 63 | init(context); 64 | } 65 | 66 | 67 | private void init(Context context) { 68 | mGestureDetector = new GestureDetector(context, getOnGestureListener()); 69 | } 70 | 71 | private GestureDetector.SimpleOnGestureListener getOnGestureListener() { 72 | return new GestureDetector.SimpleOnGestureListener() { 73 | @Override 74 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 75 | if (horizontal) { 76 | if (pagerWidth != 0) { 77 | distence = getScrollX() + (int) distanceX; 78 | if (Math.abs(distence / pagerWidth + 1) == viewList.size() + 1) { 79 | distence = 0; 80 | } 81 | } 82 | } else { 83 | if (pagerHeight != 0) { 84 | distence = getScrollY() + (int) distanceY; 85 | if (Math.abs(distence / pagerHeight + 1) == viewList.size() + 1) { 86 | distence = 0; 87 | } 88 | } 89 | } 90 | 91 | try { 92 | invate(); 93 | } catch (Exception e) { 94 | e.printStackTrace(); 95 | } 96 | return true; 97 | } 98 | }; 99 | } 100 | 101 | @Override 102 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 103 | super.onLayout(changed, l, t, r, b); 104 | if (changed) { 105 | pagerWidth = r - l; 106 | pagerHeight = b - t; 107 | } 108 | } 109 | 110 | 111 | public void setHorizontal(boolean horizontal) { 112 | try { 113 | valueAnimator.end(); 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } 117 | this.item = 0; 118 | this.horizontal = horizontal; 119 | this.requestLayout(); 120 | try { 121 | distence = 0; 122 | invate(); 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | } 126 | } 127 | 128 | 129 | public void invate() throws Exception { 130 | //Log.i("ds", "distence:" + distence); 131 | recycleAndAddView(); 132 | if (horizontal) { 133 | LoopViewPager.this.scrollTo(distence, 0); 134 | } else { 135 | LoopViewPager.this.scrollTo(0, distence); 136 | } 137 | } 138 | 139 | /*回收view*/ 140 | private void recycleAndAddView() { 141 | int next_position = 0; 142 | int nextItem = 0;//下一个 143 | int thisItem = 0; 144 | 145 | int pagerLength = 0; 146 | if (horizontal) { 147 | pagerLength = pagerWidth; 148 | } else { 149 | pagerLength = pagerHeight; 150 | } 151 | if (distence > 0) { 152 | next_position = distence / pagerLength + 1; 153 | nextItem = Math.abs(next_position % viewList.size()); 154 | 155 | thisItem = nextItem - 1; 156 | if (thisItem < 0) { 157 | thisItem = viewList.size() - 1; 158 | } 159 | if (thisItem > (viewList.size() - 1)) { 160 | thisItem = 0; 161 | } 162 | try { 163 | this.addView(viewList.get(thisItem), layoutParams); 164 | } catch (Exception e) { 165 | } 166 | viewList.get(thisItem).bringToFront(); 167 | if (horizontal) { 168 | viewList.get(thisItem).setX((next_position - 1) * pagerLength); 169 | viewList.get(thisItem).setY(0); 170 | } else { 171 | viewList.get(thisItem).setY((next_position - 1) * pagerLength); 172 | viewList.get(thisItem).setX(0); 173 | } 174 | 175 | 176 | try { 177 | this.addView(viewList.get(nextItem), layoutParams); 178 | } catch (Exception e) { 179 | } 180 | viewList.get(nextItem).bringToFront(); 181 | if (horizontal) { 182 | viewList.get(nextItem).setX(next_position * pagerLength); 183 | viewList.get(nextItem).setY(0); 184 | } else { 185 | viewList.get(nextItem).setY(next_position * pagerLength); 186 | viewList.get(nextItem).setX(0); 187 | } 188 | } else if (distence < 0) { 189 | next_position = distence / pagerLength - 1; 190 | nextItem = Math.abs(next_position % viewList.size()); 191 | thisItem = nextItem - 1; 192 | if (thisItem < 0) { 193 | thisItem = viewList.size() - 1; 194 | } 195 | if (thisItem > (viewList.size() - 1)) { 196 | thisItem = 0; 197 | } 198 | try { 199 | this.addView(viewList.get(thisItem), layoutParams); 200 | } catch (Exception e) { 201 | } 202 | viewList.get(thisItem).bringToFront(); 203 | if (horizontal) { 204 | viewList.get(thisItem).setX((next_position + 1) * pagerLength); 205 | viewList.get(thisItem).setY(0); 206 | } else { 207 | viewList.get(thisItem).setY((next_position + 1) * pagerLength); 208 | viewList.get(thisItem).setX(0); 209 | } 210 | 211 | try { 212 | this.addView(viewList.get(nextItem), layoutParams); 213 | } catch (Exception e) { 214 | } 215 | viewList.get(nextItem).bringToFront(); 216 | if (horizontal) { 217 | viewList.get(nextItem).setX(next_position * pagerLength); 218 | viewList.get(nextItem).setY(0); 219 | } else { 220 | viewList.get(nextItem).setY(next_position * pagerLength); 221 | viewList.get(nextItem).setX(0); 222 | } 223 | } else { 224 | next_position = 0; 225 | nextItem = 0; 226 | thisItem = 0; 227 | try { 228 | this.addView(viewList.get(0), layoutParams); 229 | } catch (Exception e) { 230 | } 231 | viewList.get(nextItem).bringToFront(); 232 | viewList.get(0).setX(0); 233 | viewList.get(0).setY(0); 234 | } 235 | 236 | if (distence >= 0) { 237 | position = distence / pagerLength; 238 | } else { 239 | position = distence / pagerLength - 1; 240 | } 241 | item = thisItem; 242 | /*回收*/ 243 | for (int i = 0; i < viewList.size(); i++) { 244 | if (i != nextItem && i != thisItem) { 245 | try { 246 | this.removeView(viewList.get(i)); 247 | } catch (Exception e) { 248 | } 249 | } 250 | } 251 | 252 | } 253 | 254 | @Override 255 | public boolean dispatchTouchEvent(MotionEvent ev) { 256 | try { 257 | if (viewList.size() <= 1) { 258 | return super.dispatchTouchEvent(ev); 259 | } 260 | } catch (Exception e) { 261 | } 262 | if (ev.getAction() == MotionEvent.ACTION_DOWN) { 263 | touching = true; 264 | if (horizontal) { 265 | last_touch_point = (int) ev.getX(); 266 | } else { 267 | last_touch_point = (int) ev.getY(); 268 | } 269 | 270 | try { 271 | setAuto(false); 272 | } catch (Exception e) { 273 | } 274 | } 275 | mGestureDetector.onTouchEvent(ev); 276 | if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { 277 | touching = false; 278 | if (Math.abs(ev.getX() - last_touch_point) > 10) { 279 | if (horizontal) { 280 | if (ev.getX() - last_touch_point < 0) {//left 281 | AnimationTo(getScrollX(), (position + 1) * pagerWidth); 282 | } else {//right 283 | AnimationTo(getScrollX(), (position) * pagerWidth); 284 | } 285 | } else { 286 | if (ev.getY() - last_touch_point < 0) {// 287 | AnimationTo(getScrollY(), (position + 1) * pagerHeight); 288 | } else {//right 289 | AnimationTo(getScrollY(), (position) * pagerHeight); 290 | } 291 | } 292 | } else { 293 | backToAutoChange(); 294 | } 295 | } 296 | if (Math.abs(ev.getX() - last_touch_point) > 20 && horizontal == true) { 297 | return true; 298 | } else if (Math.abs(ev.getY() - last_touch_point) > 20 && horizontal == false) { 299 | return true; 300 | } else { 301 | return super.dispatchTouchEvent(ev); 302 | } 303 | } 304 | 305 | private void backToAutoChange() { 306 | if (autoChange) { 307 | try { 308 | setAuto(true); 309 | } catch (Exception e) { 310 | e.printStackTrace(); 311 | } 312 | } 313 | } 314 | 315 | @Override 316 | public boolean onTouchEvent(MotionEvent event) { 317 | return true; 318 | } 319 | 320 | 321 | private void AnimationTo(int from, int to) { 322 | if (from == to) { 323 | return; 324 | } 325 | try { 326 | valueAnimator = ValueAnimator.ofInt(from, to); 327 | valueAnimator.setDuration(500); 328 | valueAnimator.addListener(new Animator.AnimatorListener() { 329 | @Override 330 | public void onAnimationStart(Animator animation) { 331 | 332 | } 333 | 334 | @Override 335 | public void onAnimationEnd(Animator animation) { 336 | backToAutoChange(); 337 | if (onItemSelectedListener != null) { 338 | onItemSelectedListener.selected(item, viewList.get(item)); 339 | } 340 | } 341 | 342 | @Override 343 | public void onAnimationCancel(Animator animation) { 344 | 345 | } 346 | 347 | @Override 348 | public void onAnimationRepeat(Animator animation) { 349 | 350 | } 351 | }); 352 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 353 | @Override 354 | public void onAnimationUpdate(ValueAnimator animation) { 355 | try { 356 | if (touching == false) { 357 | distence = (Integer) animation.getAnimatedValue(); 358 | invate(); 359 | } 360 | } catch (Exception e) { 361 | } 362 | } 363 | }); 364 | valueAnimator.start(); 365 | } catch (Exception e) { 366 | } 367 | } 368 | 369 | 370 | public void setCurrentItem(int item) { 371 | this.item = item; 372 | try { 373 | invate(); 374 | } catch (Exception e) { 375 | } 376 | } 377 | 378 | public int getItem() { 379 | return item; 380 | } 381 | 382 | private void setAuto(boolean auto) throws Exception { 383 | loopHandler.setLoop(auto); 384 | } 385 | 386 | public long getAutoChangeTime() { 387 | return autoChangeTime; 388 | } 389 | 390 | public void setAutoChangeTime(long autoChangeTime) { 391 | this.autoChangeTime = autoChangeTime; 392 | loopHandler.setLoopTime(autoChangeTime); 393 | } 394 | 395 | public boolean isAutoChange() { 396 | return autoChange; 397 | } 398 | 399 | public void setAutoChange(boolean change) { 400 | if (viewList.size() <= 1) { 401 | try { 402 | setAuto(false); 403 | } catch (Exception e) { 404 | } 405 | } else { 406 | autoChange = change; 407 | try { 408 | setAuto(change); 409 | } catch (Exception e) { 410 | e.printStackTrace(); 411 | } 412 | } 413 | } 414 | 415 | public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) { 416 | this.onItemSelectedListener = onItemSelectedListener; 417 | } 418 | 419 | public List getViewList() { 420 | return viewList; 421 | } 422 | 423 | public void setViewList(List viewList) { 424 | try { 425 | setAutoChange(false); 426 | } catch (Exception e) { 427 | } 428 | try { 429 | removeAllViews(); 430 | } catch (Exception e) { 431 | } 432 | this.viewList = viewList; 433 | try { 434 | invate(); 435 | } catch (Exception e) { 436 | e.printStackTrace(); 437 | } 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /loopviews/src/main/java/com/cry/loopviews/listener/OnInvateListener.java: -------------------------------------------------------------------------------- 1 | package com.cry.loopviews.listener; 2 | 3 | import com.cry.loopviews.LoopView; 4 | 5 | /** 6 | * Created by zxj on 16/1/6. 7 | */ 8 | public interface OnInvateListener { 9 | void onInvate(LoopView loopView); 10 | } 11 | -------------------------------------------------------------------------------- /loopviews/src/main/java/com/cry/loopviews/listener/OnItemSelectedListener.java: -------------------------------------------------------------------------------- 1 | package com.cry.loopviews.listener; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by zxj on 15/5/25. 7 | */ 8 | public interface OnItemSelectedListener { 9 | public void selected(int item, View view); 10 | } 11 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':example', ':loopviews' 2 | --------------------------------------------------------------------------------