├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── asiatravel │ │ └── atdragviewdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── asiatravel │ │ │ └── atdragviewdemo │ │ │ ├── customview │ │ │ ├── ATDragView.java │ │ │ ├── AndTextViewLayout.java │ │ │ ├── CountDownCircleView.java │ │ │ ├── MyElongScaleSeekBar.java │ │ │ ├── MyElongVerticalTextView.java │ │ │ └── SwitchView.java │ │ │ └── ui │ │ │ ├── ListActivity.java │ │ │ ├── MainActivity.java │ │ │ └── ViewShowActivity.java │ └── res │ │ ├── layout │ │ ├── activity_list.xml │ │ ├── activity_main.xml │ │ ├── activity_viewshow.xml │ │ ├── content_main.xml │ │ └── layout_test.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── customer_service_flight.png │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── myelong_red_expired.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── asiatravel │ └── atdragviewdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── 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 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ATDragViewDemo 2 | 自定义双向滑动的seekbar 3 | ## 目标:双向拖动的自定义View ## 4 | 5 | 国际惯例先预览后实现![这里写图片描述](http://img.blog.csdn.net/20160901105432584) 6 | 7 | 我们要实现的就是一个段位样式的拖动条,用来做筛选条件用的, 8 | 信心的朋友可能会发现微信里面有个一个通用字体的, 9 | 拖动然后改变字体大小; 10 | 11 | 这个相对比微信那个的自定义view算是一个扩展,因为我们是双向滑动,这个多考虑的一点就是手指拖动的是哪一个滑动块! 12 | 13 | 我们先看下GIF预览,然后我们今天就一步步实现这个小玩意... 14 | 15 | ![这里写图片描述](http://img.blog.csdn.net/20160901105705078) 16 | 17 | 18 | ---------- 19 | **实现步骤** 20 | 21 | - 自定义属性的抽取 22 | - view尺寸的计算 23 | - 相关内容的绘制(文字,原点,背景进度条,当前进度条等等) 24 | - 处理滑动事件 25 | 26 | 27 | 28 | ---------- 29 | 大体思路分四部分;我们一步步来;简单的就一部带过了 30 | 31 | - 自定义属性获取: 32 | 33 | 34 | 35 | ``` 36 | public ATDragView(Context context, AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | setBackgroundColor(ContextCompat.getColor(context, android.R.color.white)); 39 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATDragView, defStyleAttr, R.style.def_dragview); 40 | int indexCount = typedArray.getIndexCount(); 41 | for (int i = 0; i < indexCount; i++) { 42 | int attr = typedArray.getIndex(i); 43 | switch (attr) { 44 | case R.styleable.ATDragView_seek_bg_color: 45 | seekBgColor = typedArray.getColor(attr, Color.BLACK); 46 | break; 47 | case R.styleable.ATDragView_seek_pb_color: 48 | seekPbColor = typedArray.getColor(attr, Color.BLACK); 49 | break; 50 | case R.styleable.ATDragView_seek_ball_solid_color: 51 | seekBallSolidColor = typedArray.getColor(attr, Color.BLACK); 52 | break; 53 | case R.styleable.ATDragView_seek_ball_stroke_color: 54 | seekBallStrokeColor = typedArray.getColor(attr, Color.BLACK); 55 | break; 56 | case R.styleable.ATDragView_seek_text_color: 57 | seekTextColor = typedArray.getColor(attr, Color.BLACK); 58 | break; 59 | case R.styleable.ATDragView_seek_text_size: 60 | seekTextSize = typedArray.getDimensionPixelSize(attr, 0); 61 | break; 62 | } 63 | } 64 | typedArray.recycle(); 65 | init(); 66 | } 67 | ``` 68 | 拿到我们的属性后,初始化我们需要的工具,比如画笔,等 69 | 70 | ``` 71 | private void init() { 72 | currentMovingType = BallType.LEFT; 73 | seekTextPaint = creatPaint(seekTextColor, seekTextSize, Paint.Style.FILL, 0); 74 | seekBgPaint = creatPaint(seekBgColor, 0, Paint.Style.FILL, 0); 75 | seekBallPaint = creatPaint(seekBallSolidColor, 0, Paint.Style.FILL, 0); 76 | seekPbPaint = creatPaint(seekPbColor, 0, Paint.Style.FILL, 0); 77 | seekBallEndPaint = creatPaint(seekPbColor, 0, Paint.Style.FILL, 0); 78 | seekBallStrokePaint = creatPaint(seekBallStrokeColor, 0, Paint.Style.FILL, 0); 79 | seekBallStrokePaint.setShadowLayer(5, 2, 2, seekBallStrokeColor); 80 | } 81 | 82 | ``` 83 | 84 | - 确定自定义view尺寸 85 | 86 | 87 | ``` 88 | @Override 89 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 90 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 91 | int measureHeight; 92 | if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { 93 | measureHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_HEIGHT, getContext().getResources().getDisplayMetrics()); 94 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY); 95 | } 96 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 97 | } 98 | ``` 99 | 100 | - 绘制相关的内容部分, 101 | 这里我们分析效果图发现,需要绘制五部分,两个圆,两个进度条一个 一堆文字,我们根据计算出来的view尺寸以及UI给的比例,即可绘制出来他们这个就是canvas的API使用 102 | 103 | 104 | ``` 105 | @Override 106 | protected void onDraw(Canvas canvas) { 107 | super.onDraw(canvas); 108 | drawTexts(canvas); 109 | drawSeekBG(canvas); 110 | drawSeekPB(canvas); 111 | drawLeftCircle(canvas); 112 | drawRightCircle(canvas); 113 | } 114 | ``` 115 | 116 | 具体的文字绘制,是根据外界传入的数据来绘制的所以细节如下 117 | 118 | ``` 119 | private void drawTexts(Canvas canvas) { 120 | if (null == data) return; 121 | int size = data.size(); 122 | int unitWidth = getUnitWidth(size - 1); 123 | for (int i = 0; i < size; i++) { 124 | String tempDesc = data.get(i); 125 | float measureTextWidth = seekPbPaint.measureText(tempDesc); 126 | canvas.drawText(tempDesc, DEF_PADDING + unitWidth * i - measureTextWidth / 2, seekTextY, seekTextPaint); 127 | } 128 | } 129 | ``` 130 | 131 | 132 | ---------- 133 | **这个View的核心部分不是绘制,而是计算,描述下我们具体的确定位置的思路** 134 | 135 | 1. 根据外界传入的数据集合平均分view的宽度,求得平均一份的宽度大小 136 | 2. 然后循环数据集合根据平均一份的宽度,确定没个文字所在的坐标值 137 | 138 | 139 | 140 | ---------- 141 | 然后我们看下计算的代码; 142 | 143 | ``` 144 | // 计算单位宽度,view宽度-内容的左右边距以及圆球的半径,自己体会下为什么 145 | private int getUnitWidth(int count) { 146 | return (viewWidth - 2 * DEF_PADDING - 2 * seekBallRadio) / count; 147 | } 148 | ``` 149 | **这个方法可以说是最重要的一个了,** 150 | ``` 151 | //根据当前手指触摸的x坐标计算,手指离开屏幕以后,应该停留到哪个位置,比如滑动到400到500之间但是不到600,我们不能让他停留在半路上,让他自动找回他停留的左边,也就是GIF中的小小回弹效果 152 | private int getCurrentSeekX(int upX) { 153 | if (null == data) { 154 | return 0; 155 | } 156 | int unitWidth = getUnitWidth(data.size() - 1); 157 | return unitWidth * (upX / unitWidth); 158 | } 159 | ``` 160 | 161 | 162 | ---------- 163 | **核心的代码全部完毕了,我们看下onTouch里面的处理** 164 | 165 | ``` 166 | public boolean onTouchEvent(MotionEvent event) { 167 | 168 | switch (event.getAction()) { 169 | case MotionEvent.ACTION_DOWN: 170 | //记录手指按下的坐标 171 | downX = (int) event.getX(); 172 | // 根据当前坐标,确定要移动哪个球球,因为我们说了,我们这个是有两个球的,唯一的一个技巧点就是这个地方,根据手指按下的坐标找到距离哪个球位置最近就移动哪个球,这里注意下. 173 | currentMovingType = getMovingLeftOrRight(downX); 174 | if (BallType.LEFT == currentMovingType) { 175 | leftSeekBallX = downX; 176 | } else if (BallType.RIGHT == currentMovingType) { 177 | rightSeekBallX = downX; 178 | } 179 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 180 | 181 | break; 182 | case MotionEvent.ACTION_MOVE: 183 | //移动的时候根据计算出来的位置以及方向改变两个小球的位置以及举行进度条的RectF的范围 184 | int moveX = (int) event.getX(); 185 | // 特殊情况处理,两个球重合应该怎么办, 186 | if (leftSeekBallX == rightSeekBallX) { 187 | if (moveX - downX > 0) { 188 | currentMovingType = BallType.RIGHT; 189 | rightSeekBallX = moveX; 190 | } else { 191 | currentMovingType = BallType.LEFT; 192 | leftSeekBallX = moveX; 193 | } 194 | } else { 195 | if (BallType.LEFT == currentMovingType) { 196 | leftSeekBallX = leftSeekBallX - rightSeekBallX >= 0 ? rightSeekBallX : moveX; 197 | } else if (BallType.RIGHT == currentMovingType) { 198 | rightSeekBallX = rightSeekBallX - leftSeekBallX <= 0 ? leftSeekBallX : moveX; 199 | } 200 | } 201 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 202 | break; 203 | case MotionEvent.ACTION_UP: 204 | // 手指离开的时候,确定返回给UI的数据集 205 | if (BallType.LEFT == currentMovingType) { 206 | leftPosition = getDataPosition((int) event.getX()); 207 | leftSeekBallX = leftSeekBallX - rightSeekBallX >= 0 ? rightSeekBallX : getCurrentSeekX((int) event.getX()) + DEF_PADDING + seekBallRadio; 208 | } else if (BallType.RIGHT == currentMovingType) { 209 | rightPosition = getDataPosition((int) event.getX()); 210 | rightSeekBallX = rightSeekBallX - leftSeekBallX <= 0 ? leftSeekBallX : getCurrentSeekX((int) event.getX()) + DEF_PADDING + seekBallRadio; 211 | } 212 | if (null != dragFinishedListener) { 213 | dragFinishedListener.dragFinished(leftPosition, rightPosition); 214 | } 215 | break; 216 | } 217 | // 边界处理,确保左边的球不会超过右边的,右边的不会超过左边的 218 | if (BallType.LEFT == currentMovingType) { 219 | if (leftSeekBallX < seekBallRadio + DEF_PADDING) { 220 | leftSeekBallX = seekBallRadio + DEF_PADDING; 221 | } 222 | if (leftSeekBallX > viewWidth - seekBallRadio - DEF_PADDING) { 223 | leftSeekBallX = viewWidth - seekBallRadio - DEF_PADDING; 224 | } 225 | } else if (BallType.RIGHT == currentMovingType) { 226 | if (rightSeekBallX < seekBallRadio + DEF_PADDING) { 227 | rightSeekBallX = seekBallRadio + DEF_PADDING; 228 | } 229 | if (rightSeekBallX > viewWidth - seekBallRadio - DEF_PADDING) { 230 | rightSeekBallX = viewWidth - seekBallRadio - DEF_PADDING; 231 | } 232 | } 233 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 234 | invalidate(); 235 | return true; 236 | } 237 | ``` 238 | 239 | 240 | ---------- 241 | **大部分的核心的代码就这么多,然后剩下的view写完了就该把回调借口透出给UI 完活了.....** 242 | 243 | ``` 244 | public void setData(List data, OnDragFinishedListener dragFinishedListener) { 245 | this.dragFinishedListener = dragFinishedListener; 246 | this.data = data; 247 | leftPosition = 0; 248 | if (null != data && data.size() != 0) { 249 | rightPosition = data.size() - 1; 250 | } 251 | } 252 | ``` 253 | 254 | ## 写了这么多重要的还是这个地方源代码下载地址[https://github.com/GuoFeilong/ATDragViewDemo](https://github.com/GuoFeilong/ATDragViewDemo) ## 255 | 256 | ## 希望大家多多多star一下谢谢 ## 257 | 258 | 259 | ---------- 260 | ## END ## 261 | 262 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.0" 6 | defaultConfig { 7 | applicationId "com.asiatravel.atdragviewdemo" 8 | minSdkVersion 16 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.0' 28 | compile 'com.android.support:design:24.2.0' 29 | testCompile 'junit:junit:4.12' 30 | compile 'com.orhanobut:logger:1.15' 31 | } 32 | -------------------------------------------------------------------------------- /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/jsion/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/androidTest/java/com/asiatravel/atdragviewdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.asiatravel.atdragviewdemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/customview/ATDragView.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.customview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.support.v4.content.ContextCompat; 10 | import android.util.AttributeSet; 11 | import android.util.TypedValue; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.widget.SeekBar; 15 | 16 | import com.asiatravel.atdragviewdemo.R; 17 | import com.orhanobut.logger.Logger; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Created by jsion on 16/8/30. 24 | */ 25 | 26 | public class ATDragView extends View { 27 | private static final float SEEK_BG_SCALE = 1.1F / 2; 28 | private static final float SEEK_TEXT_SCALE = 1.F / 3.5F; 29 | private static final int DEF_HEIGHT = 125; 30 | private static final int DEF_PADDING = 50; 31 | private static final int BG_HEIGHT = 5; 32 | private static final int SEEK_STROKE_SIZE = 1; 33 | 34 | private int viewWidth; 35 | private int viewHeight; 36 | private int seekBgColor; 37 | private int seekPbColor; 38 | private int seekBallSolidColor; 39 | private int seekBallStrokeColor; 40 | private int seekTextColor; 41 | private int seekTextSize; 42 | 43 | private Paint seekBgPaint; 44 | private Paint seekBallPaint; 45 | private Paint seekBallEndPaint; 46 | private Paint seekBallStrokePaint; 47 | private Paint seekPbPaint; 48 | private Paint seekTextPaint; 49 | private RectF seekBGRectF; 50 | private RectF seekPbRectF; 51 | 52 | private int seekBallRadio; 53 | private int seekBallY; 54 | private int leftSeekBallX; 55 | private int rightSeekBallX; 56 | 57 | private List data; 58 | private int seekTextY; 59 | private int currentMovingType; 60 | private OnDragFinishedListener dragFinishedListener; 61 | private int leftPosition, rightPosition; 62 | private int downX; 63 | 64 | public ATDragView(Context context) { 65 | this(context, null); 66 | } 67 | 68 | public ATDragView(Context context, AttributeSet attrs) { 69 | this(context, attrs, 0); 70 | } 71 | 72 | public ATDragView(Context context, AttributeSet attrs, int defStyleAttr) { 73 | super(context, attrs, defStyleAttr); 74 | setBackgroundColor(ContextCompat.getColor(context, android.R.color.white)); 75 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATDragView, defStyleAttr, R.style.def_dragview); 76 | int indexCount = typedArray.getIndexCount(); 77 | for (int i = 0; i < indexCount; i++) { 78 | int attr = typedArray.getIndex(i); 79 | switch (attr) { 80 | case R.styleable.ATDragView_seek_bg_color: 81 | seekBgColor = typedArray.getColor(attr, Color.BLACK); 82 | break; 83 | case R.styleable.ATDragView_seek_pb_color: 84 | seekPbColor = typedArray.getColor(attr, Color.BLACK); 85 | break; 86 | case R.styleable.ATDragView_seek_ball_solid_color: 87 | seekBallSolidColor = typedArray.getColor(attr, Color.BLACK); 88 | break; 89 | case R.styleable.ATDragView_seek_ball_stroke_color: 90 | seekBallStrokeColor = typedArray.getColor(attr, Color.BLACK); 91 | break; 92 | case R.styleable.ATDragView_seek_text_color: 93 | seekTextColor = typedArray.getColor(attr, Color.BLACK); 94 | break; 95 | case R.styleable.ATDragView_seek_text_size: 96 | seekTextSize = typedArray.getDimensionPixelSize(attr, 0); 97 | break; 98 | } 99 | } 100 | typedArray.recycle(); 101 | init(); 102 | } 103 | 104 | private void init() { 105 | currentMovingType = BallType.LEFT; 106 | seekTextPaint = creatPaint(seekTextColor, seekTextSize, Paint.Style.FILL, 0); 107 | seekBgPaint = creatPaint(seekBgColor, 0, Paint.Style.FILL, 0); 108 | seekBallPaint = creatPaint(seekBallSolidColor, 0, Paint.Style.FILL, 0); 109 | seekPbPaint = creatPaint(seekPbColor, 0, Paint.Style.FILL, 0); 110 | seekBallEndPaint = creatPaint(seekPbColor, 0, Paint.Style.FILL, 0); 111 | seekBallStrokePaint = creatPaint(seekBallStrokeColor, 0, Paint.Style.FILL, 0); 112 | seekBallStrokePaint.setShadowLayer(5, 2, 2, seekBallStrokeColor); 113 | } 114 | 115 | @Override 116 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 117 | super.onSizeChanged(w, h, oldw, oldh); 118 | viewHeight = h; 119 | viewWidth = w; 120 | 121 | seekBallRadio = 30; 122 | seekBallY = (int) (viewHeight * SEEK_BG_SCALE + BG_HEIGHT / 2.F); 123 | seekTextY = (int) (viewHeight * SEEK_TEXT_SCALE); 124 | leftSeekBallX = seekBallRadio + DEF_PADDING; 125 | rightSeekBallX = viewWidth - seekBallRadio - DEF_PADDING; 126 | 127 | seekBGRectF = new RectF(seekBallRadio + DEF_PADDING, viewHeight * SEEK_BG_SCALE, viewWidth - seekBallRadio - DEF_PADDING, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 128 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 129 | 130 | } 131 | 132 | @Override 133 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 134 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 135 | int measureHeight; 136 | if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { 137 | measureHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_HEIGHT, getContext().getResources().getDisplayMetrics()); 138 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY); 139 | } 140 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 141 | } 142 | 143 | @Override 144 | protected void onDraw(Canvas canvas) { 145 | super.onDraw(canvas); 146 | drawTexts(canvas); 147 | drawSeekBG(canvas); 148 | drawSeekPB(canvas); 149 | drawLeftCircle(canvas); 150 | drawRightCircle(canvas); 151 | } 152 | 153 | private void drawTexts(Canvas canvas) { 154 | if (null == data) return; 155 | int size = data.size(); 156 | int unitWidth = getUnitWidth(size - 1); 157 | for (int i = 0; i < size; i++) { 158 | String tempDesc = data.get(i); 159 | float measureTextWidth = seekPbPaint.measureText(tempDesc); 160 | canvas.drawText(tempDesc, DEF_PADDING + unitWidth * i - measureTextWidth / 2, seekTextY, seekTextPaint); 161 | } 162 | } 163 | 164 | @Override 165 | public boolean onTouchEvent(MotionEvent event) { 166 | 167 | switch (event.getAction()) { 168 | case MotionEvent.ACTION_DOWN: 169 | downX = (int) event.getX(); 170 | currentMovingType = getMovingLeftOrRight(downX); 171 | if (BallType.LEFT == currentMovingType) { 172 | leftSeekBallX = downX; 173 | } else if (BallType.RIGHT == currentMovingType) { 174 | rightSeekBallX = downX; 175 | } 176 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 177 | 178 | break; 179 | case MotionEvent.ACTION_MOVE: 180 | int moveX = (int) event.getX(); 181 | if (leftSeekBallX == rightSeekBallX) { 182 | if (moveX - downX > 0) { 183 | currentMovingType = BallType.RIGHT; 184 | rightSeekBallX = moveX; 185 | } else { 186 | currentMovingType = BallType.LEFT; 187 | leftSeekBallX = moveX; 188 | } 189 | } else { 190 | if (BallType.LEFT == currentMovingType) { 191 | leftSeekBallX = leftSeekBallX - rightSeekBallX >= 0 ? rightSeekBallX : moveX; 192 | } else if (BallType.RIGHT == currentMovingType) { 193 | rightSeekBallX = rightSeekBallX - leftSeekBallX <= 0 ? leftSeekBallX : moveX; 194 | } 195 | } 196 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 197 | break; 198 | case MotionEvent.ACTION_UP: 199 | if (BallType.LEFT == currentMovingType) { 200 | leftPosition = getDataPosition((int) event.getX()); 201 | leftSeekBallX = leftSeekBallX - rightSeekBallX >= 0 ? rightSeekBallX : getCurrentSeekX((int) event.getX()) + DEF_PADDING + seekBallRadio; 202 | } else if (BallType.RIGHT == currentMovingType) { 203 | rightPosition = getDataPosition((int) event.getX()); 204 | rightSeekBallX = rightSeekBallX - leftSeekBallX <= 0 ? leftSeekBallX : getCurrentSeekX((int) event.getX()) + DEF_PADDING + seekBallRadio; 205 | } 206 | if (null != dragFinishedListener) { 207 | dragFinishedListener.dragFinished(leftPosition, rightPosition); 208 | } 209 | break; 210 | } 211 | 212 | if (BallType.LEFT == currentMovingType) { 213 | if (leftSeekBallX < seekBallRadio + DEF_PADDING) { 214 | leftSeekBallX = seekBallRadio + DEF_PADDING; 215 | } 216 | if (leftSeekBallX > viewWidth - seekBallRadio - DEF_PADDING) { 217 | leftSeekBallX = viewWidth - seekBallRadio - DEF_PADDING; 218 | } 219 | } else if (BallType.RIGHT == currentMovingType) { 220 | if (rightSeekBallX < seekBallRadio + DEF_PADDING) { 221 | rightSeekBallX = seekBallRadio + DEF_PADDING; 222 | } 223 | if (rightSeekBallX > viewWidth - seekBallRadio - DEF_PADDING) { 224 | rightSeekBallX = viewWidth - seekBallRadio - DEF_PADDING; 225 | } 226 | } 227 | seekPbRectF = new RectF(leftSeekBallX, viewHeight * SEEK_BG_SCALE, rightSeekBallX, viewHeight * SEEK_BG_SCALE + BG_HEIGHT); 228 | invalidate(); 229 | return true; 230 | } 231 | 232 | private void drawSeekPB(Canvas canvas) { 233 | canvas.drawRect(seekPbRectF, seekPbPaint); 234 | } 235 | 236 | private void drawRightCircle(Canvas canvas) { 237 | setLayerType(LAYER_TYPE_SOFTWARE, null); 238 | canvas.drawCircle(rightSeekBallX, seekBallY, seekBallRadio, seekBallStrokePaint); 239 | canvas.drawCircle(rightSeekBallX, seekBallY, seekBallRadio - SEEK_STROKE_SIZE, seekBallEndPaint); 240 | } 241 | 242 | private void drawLeftCircle(Canvas canvas) { 243 | setLayerType(LAYER_TYPE_SOFTWARE, null); 244 | canvas.drawCircle(leftSeekBallX, seekBallY, seekBallRadio, seekBallStrokePaint); 245 | canvas.drawCircle(leftSeekBallX, seekBallY, seekBallRadio - SEEK_STROKE_SIZE, seekBallPaint); 246 | } 247 | 248 | private void drawSeekBG(Canvas canvas) { 249 | canvas.drawRect(seekBGRectF, seekBgPaint); 250 | } 251 | 252 | 253 | private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) { 254 | Paint paint = new Paint(); 255 | paint.setColor(paintColor); 256 | paint.setAntiAlias(true); 257 | paint.setStrokeWidth(lineWidth); 258 | paint.setDither(true); 259 | paint.setTextSize(textSize); 260 | paint.setStyle(style); 261 | paint.setStrokeCap(Paint.Cap.ROUND); 262 | paint.setStrokeJoin(Paint.Join.ROUND); 263 | return paint; 264 | } 265 | 266 | private int getUnitWidth(int count) { 267 | return (viewWidth - 2 * DEF_PADDING - 2 * seekBallRadio) / count; 268 | } 269 | 270 | private int getCurrentSeekX(int upX) { 271 | if (null == data) { 272 | return 0; 273 | } 274 | int unitWidth = getUnitWidth(data.size() - 1); 275 | return unitWidth * (upX / unitWidth); 276 | } 277 | 278 | private int getDataPosition(int upX) { 279 | if (null == data) { 280 | return 0; 281 | } 282 | int unitWidth = getUnitWidth(data.size() - 1); 283 | return upX / unitWidth; 284 | } 285 | 286 | public void setData(List data, OnDragFinishedListener dragFinishedListener) { 287 | this.dragFinishedListener = dragFinishedListener; 288 | this.data = data; 289 | leftPosition = 0; 290 | if (null != data && data.size() != 0) { 291 | rightPosition = data.size() - 1; 292 | } 293 | } 294 | 295 | private int getMovingLeftOrRight(int actionX) { 296 | return Math.abs(leftSeekBallX - actionX) - Math.abs(rightSeekBallX - actionX) > 0 ? BallType.RIGHT : BallType.LEFT; 297 | } 298 | 299 | private static class BallType { 300 | private static final int LEFT = 99; 301 | private static final int RIGHT = 98; 302 | } 303 | 304 | public interface OnDragFinishedListener { 305 | void dragFinished(int leftPostion, int rightPostion); 306 | } 307 | 308 | @Override 309 | protected void onWindowVisibilityChanged(int visibility) { 310 | super.onWindowVisibilityChanged(visibility); 311 | invalidate(); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/customview/AndTextViewLayout.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.customview; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.Gravity; 10 | import android.view.animation.BounceInterpolator; 11 | import android.view.animation.LinearInterpolator; 12 | import android.widget.LinearLayout; 13 | import android.widget.TextView; 14 | 15 | import com.asiatravel.atdragviewdemo.R; 16 | 17 | /** 18 | * Created by feilong.guo on 16/11/21. 19 | */ 20 | 21 | public class AndTextViewLayout extends LinearLayout { 22 | private int andTextColor; 23 | private int andTextSize; 24 | private int andTextBbackgroundColor; 25 | private boolean andTextAnimUp; 26 | private boolean andTextAnimDown; 27 | private boolean andTextAnimLeft; 28 | private boolean andTextAnimRight; 29 | private int aniDuration; 30 | private String andTextDesc; 31 | 32 | private TextView andTextView; 33 | 34 | public AndTextViewLayout(Context context) { 35 | this(context, null); 36 | } 37 | 38 | public AndTextViewLayout(Context context, AttributeSet attrs) { 39 | this(context, attrs, 0); 40 | } 41 | 42 | public AndTextViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { 43 | super(context, attrs, defStyleAttr); 44 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AndTextViewLayout, defStyleAttr, R.style.def_and_text_layout); 45 | int indexCount = typedArray.getIndexCount(); 46 | for (int i = 0; i < indexCount; i++) { 47 | int attr = typedArray.getIndex(i); 48 | switch (attr) { 49 | case R.styleable.AndTextViewLayout_and_text_color: 50 | andTextColor = typedArray.getColor(attr, 0); 51 | break; 52 | case R.styleable.AndTextViewLayout_and_text_size: 53 | andTextSize = typedArray.getInteger(attr, 0); 54 | break; 55 | case R.styleable.AndTextViewLayout_and_text_background_color: 56 | andTextBbackgroundColor = typedArray.getColor(attr, 0); 57 | break; 58 | case R.styleable.AndTextViewLayout_and_text_anim_duration_second: 59 | aniDuration = typedArray.getInteger(attr, 0) * 1000; 60 | break; 61 | case R.styleable.AndTextViewLayout_and_text_anim_up: 62 | andTextAnimUp = typedArray.getBoolean(attr, false); 63 | break; 64 | case R.styleable.AndTextViewLayout_and_text_anim_down: 65 | andTextAnimDown = typedArray.getBoolean(attr, false); 66 | break; 67 | case R.styleable.AndTextViewLayout_and_text_anim_left: 68 | andTextAnimLeft = typedArray.getBoolean(attr, false); 69 | break; 70 | case R.styleable.AndTextViewLayout_and_text_anim_right: 71 | andTextAnimRight = typedArray.getBoolean(attr, true); 72 | break; 73 | case R.styleable.AndTextViewLayout_and_text_desc: 74 | andTextDesc = typedArray.getString(attr); 75 | break; 76 | } 77 | } 78 | typedArray.recycle(); 79 | initAndTextView(); 80 | 81 | } 82 | 83 | private void initAndTextView() { 84 | andTextView = new TextView(getContext()); 85 | LinearLayout.LayoutParams layoutParams = new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 86 | andTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, andTextSize); 87 | andTextView.setTextColor(andTextColor); 88 | andTextView.setText(andTextDesc); 89 | andTextView.setBackgroundColor(andTextBbackgroundColor); 90 | layoutParams.gravity = Gravity.CENTER_VERTICAL; 91 | andTextView.setLayoutParams(layoutParams); 92 | addView(andTextView); 93 | } 94 | 95 | public void startAndTextAnim() { 96 | ObjectAnimator objectAnimator = creatCurrentAnimation(); 97 | objectAnimator.setDuration(aniDuration); 98 | objectAnimator.setInterpolator(new LinearInterpolator()); 99 | objectAnimator.setRepeatCount(ValueAnimator.INFINITE); 100 | objectAnimator.start(); 101 | } 102 | 103 | private ObjectAnimator creatCurrentAnimation() { 104 | ObjectAnimator objectAnimator = null; 105 | if (andTextAnimRight) { 106 | objectAnimator = ObjectAnimator.ofFloat(andTextView, "translationX", -getWidth(), getWidth() + andTextView.getWidth()); 107 | } else if (andTextAnimLeft) { 108 | objectAnimator = ObjectAnimator.ofFloat(andTextView, "translationX", getWidth() + andTextView.getWidth(), -getWidth()); 109 | } else if (andTextAnimUp) { 110 | objectAnimator = ObjectAnimator.ofFloat(andTextView, "translationY", -getHeight(), getHeight() + andTextView.getHeight()); 111 | } else if (andTextAnimDown) { 112 | objectAnimator = ObjectAnimator.ofFloat(andTextView, "translationY", getHeight() + andTextView.getHeight(), -getHeight()); 113 | } 114 | return objectAnimator; 115 | } 116 | 117 | @Override 118 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 119 | super.onSizeChanged(w, h, oldw, oldh); 120 | // 测试 121 | startAndTextAnim(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/customview/CountDownCircleView.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.customview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.Point; 12 | import android.graphics.RectF; 13 | import android.util.AttributeSet; 14 | import android.util.TypedValue; 15 | import android.view.MotionEvent; 16 | import android.view.View; 17 | import android.view.animation.LinearInterpolator; 18 | 19 | import com.asiatravel.atdragviewdemo.R; 20 | 21 | /** 22 | * Created by user on 16/9/19. 23 | */ 24 | 25 | public class CountDownCircleView extends View { 26 | private static final int VIEW_DEF_SIZE = 60; 27 | private static final long DEF_ANIMATION_TIME = 3000; 28 | 29 | private int circleBgColor; 30 | private int circlePbColor; 31 | private int circlePbWidth; 32 | private int circleTextColor; 33 | private int circleTextSize; 34 | 35 | private int circleViewSize; 36 | private int circleRadio; 37 | private Point circleCenter; 38 | 39 | private Paint circleBgPaint; 40 | private Paint circlePbPaint; 41 | private Paint circleTextPaint; 42 | private RectF pbOval; 43 | private String circleDesc; 44 | private Point circleDescPosition; 45 | private float endSweepAngel; 46 | private ValueAnimator valueAnimator; 47 | private OnCountDownFinishedListener countDownFinishedListener; 48 | 49 | public CountDownCircleView(Context context) { 50 | this(context, null); 51 | } 52 | 53 | public CountDownCircleView(Context context, AttributeSet attrs) { 54 | this(context, attrs, 0); 55 | } 56 | 57 | public CountDownCircleView(Context context, AttributeSet attrs, int defStyleAttr) { 58 | super(context, attrs, defStyleAttr); 59 | initAttrWithStyle(context, attrs, defStyleAttr); 60 | initData(); 61 | } 62 | 63 | private void initData() { 64 | circleBgPaint = creatPaint(circleBgColor, 0, Paint.Style.FILL, 0); 65 | circlePbPaint = creatPaint(circlePbColor, 0, Paint.Style.STROKE, circlePbWidth); 66 | circleTextPaint = creatPaint(circleTextColor, circleTextSize, Paint.Style.FILL, 0); 67 | circleCenter = new Point(); 68 | circleDesc = "跳过"; 69 | valueAnimator = getValA(0.F, 1.F); 70 | } 71 | 72 | private void initAttrWithStyle(Context context, AttributeSet attrs, int defStyleAttr) { 73 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CountDownCircleView, defStyleAttr, R.style.def_countdown_circle_view); 74 | int indexCount = typedArray.getIndexCount(); 75 | for (int i = 0; i < indexCount; i++) { 76 | int attr = typedArray.getIndex(i); 77 | switch (attr) { 78 | case R.styleable.CountDownCircleView_circle_bg_color: 79 | circleBgColor = typedArray.getColor(attr, Color.BLACK); 80 | break; 81 | case R.styleable.CountDownCircleView_circle_pb_color: 82 | circlePbColor = typedArray.getColor(attr, Color.BLACK); 83 | break; 84 | case R.styleable.CountDownCircleView_circle_pb_width: 85 | circlePbWidth = typedArray.getDimensionPixelOffset(attr, 0); 86 | break; 87 | case R.styleable.CountDownCircleView_circle_text_color: 88 | circleTextColor = typedArray.getColor(attr, Color.BLACK); 89 | break; 90 | case R.styleable.CountDownCircleView_circle_text_size: 91 | circleTextSize = typedArray.getDimensionPixelSize(attr, 0); 92 | break; 93 | } 94 | } 95 | typedArray.recycle(); 96 | } 97 | 98 | @Override 99 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 100 | super.onSizeChanged(w, h, oldw, oldh); 101 | circleViewSize = Math.min(w, h); 102 | circleRadio = circleViewSize / 2; 103 | circleCenter.set(w / 2, h / 2); 104 | pbOval = new RectF(circlePbWidth, circlePbWidth, circleViewSize - circlePbWidth, circleViewSize - circlePbWidth); 105 | circleDescPosition = calculateTextRange(circleDesc); 106 | } 107 | 108 | private Point calculateTextRange(String circleDesc) { 109 | Point point = new Point(); 110 | int textW = (int) circleTextPaint.measureText(circleDesc); 111 | point.set(circleViewSize / 2 - textW / 2, circleViewSize / 2 + getFontHeight(circleTextSize) / 2); 112 | return point; 113 | } 114 | 115 | public int getFontHeight(float fontSize) { 116 | Paint paint = new Paint(); 117 | paint.setTextSize(fontSize); 118 | Paint.FontMetrics fm = paint.getFontMetrics(); 119 | return (int) Math.ceil(fm.descent - fm.ascent) - 4; 120 | } 121 | 122 | @Override 123 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 124 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 125 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 126 | 127 | int widthMeasure = 0; 128 | int heightMeasure = 0; 129 | 130 | if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) { 131 | widthMeasure = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, VIEW_DEF_SIZE, getContext().getResources().getDisplayMetrics()); 132 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthMeasure, MeasureSpec.EXACTLY); 133 | } 134 | 135 | if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { 136 | heightMeasure = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, VIEW_DEF_SIZE, getResources().getDisplayMetrics()); 137 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMeasure, MeasureSpec.EXACTLY); 138 | } 139 | 140 | int viewMeasureSpec = widthMeasure - heightMeasure >= 0 ? heightMeasureSpec : widthMeasureSpec; 141 | super.onMeasure(viewMeasureSpec, viewMeasureSpec); 142 | } 143 | 144 | @Override 145 | protected void onDraw(Canvas canvas) { 146 | super.onDraw(canvas); 147 | drawCircleBg(canvas); 148 | drawCirclePb(canvas); 149 | drawCircleText(canvas); 150 | } 151 | 152 | private void drawCircleText(Canvas canvas) { 153 | canvas.drawText(circleDesc, circleDescPosition.x, circleDescPosition.y, circleTextPaint); 154 | } 155 | 156 | private void drawCirclePb(Canvas canvas) { 157 | canvas.drawArc(pbOval, -90, endSweepAngel, false, circlePbPaint); 158 | } 159 | 160 | private void drawCircleBg(Canvas canvas) { 161 | canvas.drawCircle(circleCenter.x, circleCenter.y, circleRadio, circleBgPaint); 162 | } 163 | 164 | @Override 165 | protected void onDetachedFromWindow() { 166 | super.onDetachedFromWindow(); 167 | if (null != valueAnimator) { 168 | valueAnimator.cancel(); 169 | } 170 | } 171 | 172 | private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) { 173 | Paint paint = new Paint(); 174 | paint.setColor(paintColor); 175 | paint.setAntiAlias(true); 176 | paint.setStrokeWidth(lineWidth); 177 | paint.setDither(true); 178 | paint.setTextSize(textSize); 179 | paint.setStyle(style); 180 | paint.setStrokeCap(Paint.Cap.ROUND); 181 | paint.setStrokeJoin(Paint.Join.ROUND); 182 | return paint; 183 | } 184 | 185 | private ValueAnimator getValA(float start, float end) { 186 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end); 187 | valueAnimator.setDuration(DEF_ANIMATION_TIME); 188 | valueAnimator.setInterpolator(new LinearInterpolator()); 189 | valueAnimator.setRepeatCount(0); 190 | return valueAnimator; 191 | } 192 | 193 | public void startCountDown(final OnCountDownFinishedListener countDownFinishedListener) { 194 | this.countDownFinishedListener = countDownFinishedListener; 195 | if (null != valueAnimator) { 196 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 197 | @Override 198 | public void onAnimationUpdate(ValueAnimator animation) { 199 | endSweepAngel = 360 * Float.valueOf(animation.getAnimatedValue().toString()); 200 | invalidate(); 201 | } 202 | }); 203 | valueAnimator.addListener(new AnimatorListenerAdapter() { 204 | @Override 205 | public void onAnimationEnd(Animator animation) { 206 | super.onAnimationEnd(animation); 207 | if (null != countDownFinishedListener) { 208 | countDownFinishedListener.countDownStop(); 209 | valueAnimator.cancel(); 210 | } 211 | } 212 | }); 213 | valueAnimator.start(); 214 | } 215 | } 216 | 217 | @Override 218 | public boolean onTouchEvent(MotionEvent event) { 219 | switch (event.getAction()) { 220 | case MotionEvent.ACTION_UP: 221 | if (null != countDownFinishedListener) { 222 | countDownFinishedListener.countDownStop(); 223 | valueAnimator.cancel(); 224 | } 225 | break; 226 | } 227 | return true; 228 | } 229 | 230 | public interface OnCountDownFinishedListener { 231 | void countDownStop(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/customview/MyElongScaleSeekBar.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.customview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.graphics.Region; 11 | import android.support.annotation.NonNull; 12 | import android.support.annotation.Nullable; 13 | import android.text.TextUtils; 14 | import android.util.AttributeSet; 15 | import android.util.Log; 16 | import android.view.MotionEvent; 17 | import android.view.View; 18 | 19 | 20 | import com.asiatravel.atdragviewdemo.R; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * @author feilong 27 | */ 28 | public class MyElongScaleSeekBar extends View { 29 | private static final String TAG = "MyElongScaleSeekBar"; 30 | private int scaleProgressNormalColor; 31 | private int scaleProgressSectionColor; 32 | private int scaleLeftBallBgColor; 33 | private int scaleLeftBallStrokeColor; 34 | private int scaleLeftBallStrokeWith; 35 | private int scaleRightBallBgColor; 36 | private int scaleRightBallStrokeColor; 37 | private int scaleRightBallStrokeWith; 38 | private int scaleBallRadio; 39 | private int scaleSeekHeight; 40 | private int scaleLeftTextColor; 41 | private int scaleLeftTextSize; 42 | private int scaleRightTextColor; 43 | private int scaleRightTextSize; 44 | private int scaleTextMarginBall; 45 | private int scaleBallShadowColor; 46 | private int scaleBallShadowRadio; 47 | private int scaleBallStickHeight; 48 | private int scaleBallStickWidth; 49 | private int scaleBallStickColor; 50 | private int scaleBallStickMargin; 51 | 52 | private Paint scaleProgressNormalPaint; 53 | private Paint scaleProgressSectionPaint; 54 | private Paint scaleLeftBallBgPaint; 55 | private Paint scaleLeftBallStrokePaint; 56 | private Paint scaleRightBallBgPaint; 57 | private Paint scaleRightBallStrokePaint; 58 | private Paint scaleLeftTextPaint; 59 | private Paint scaleRightTextPaint; 60 | private Paint scaleStickPaint; 61 | 62 | private RectF scaleSeekNormalRectF; 63 | private RectF scaleSeekSectionRectF; 64 | private SeekPoint leftBallPoint; 65 | private SeekPoint rightBallPoint; 66 | private float radioWithStroke; 67 | 68 | private RectF stickLeftRectF1; 69 | private RectF stickLeftRectF2; 70 | private RectF stickLeftRectF3; 71 | 72 | private RectF stickRightRectF1; 73 | private RectF stickRightRectF2; 74 | private RectF stickRightRectF3; 75 | 76 | private Path scaleLeftBallPath; 77 | private Path scaleRightBallPath; 78 | 79 | private Region scaleLeftBallRegion; 80 | private Region scaleRightBallRegion; 81 | /** 82 | * 拼接在数据之前要展示的符号 83 | */ 84 | private String symbolFront; 85 | /** 86 | * 左边的文案描述 87 | */ 88 | private String leftDesc; 89 | /** 90 | * 右边的文案描述 91 | */ 92 | private String rightDesc; 93 | 94 | /** 95 | * 进度条的最大值 96 | */ 97 | private int maxValue; 98 | /** 99 | * 进度条步长(不设置,默认是50一步) 100 | */ 101 | private int scaleProgressUnit; 102 | /** 103 | * 单位步长对应的X轴距离 104 | */ 105 | private float xCoordinateUnit; 106 | /** 107 | * 当前左边球的数据实体 108 | */ 109 | private UnitValueEntity currentLeft; 110 | /** 111 | * 当前右边球的数据实体 112 | */ 113 | private UnitValueEntity currentRight; 114 | /** 115 | * 尺寸数据源 116 | */ 117 | private List valueEntities; 118 | 119 | private OnSeekBarDragListener seekBarDragListener; 120 | 121 | 122 | public MyElongScaleSeekBar(Context context) { 123 | this(context, null); 124 | } 125 | 126 | public MyElongScaleSeekBar(Context context, @Nullable AttributeSet attrs) { 127 | this(context, attrs, 0); 128 | } 129 | 130 | public MyElongScaleSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 131 | super(context, attrs, defStyleAttr); 132 | initAttrs(context, attrs); 133 | initPaints(); 134 | } 135 | 136 | 137 | public void setMaxValue(int maxValue, @Nullable OnSeekBarDragListener seekBarDragListener, int scaleProgressUnit) { 138 | this.seekBarDragListener = seekBarDragListener; 139 | this.maxValue = maxValue; 140 | this.scaleProgressUnit = scaleProgressUnit; 141 | } 142 | 143 | /** 144 | * 初始化画笔 145 | */ 146 | private void initPaints() { 147 | scaleProgressNormalPaint = creatPaint(scaleProgressNormalColor, 0, Paint.Style.FILL, 0); 148 | scaleProgressSectionPaint = creatPaint(scaleProgressSectionColor, 0, Paint.Style.FILL, 0); 149 | scaleLeftBallBgPaint = creatPaint(scaleLeftBallBgColor, 0, Paint.Style.FILL, 0); 150 | scaleLeftBallStrokePaint = creatPaint(scaleLeftBallStrokeColor, 0, Paint.Style.FILL, 0); 151 | scaleRightBallBgPaint = creatPaint(scaleRightBallBgColor, 0, Paint.Style.FILL, 0); 152 | scaleRightBallStrokePaint = creatPaint(scaleRightBallStrokeColor, 0, Paint.Style.FILL, 0); 153 | scaleLeftTextPaint = creatPaint(scaleLeftTextColor, scaleLeftTextSize, Paint.Style.FILL, 0); 154 | scaleRightTextPaint = creatPaint(scaleRightTextColor, scaleRightTextSize, Paint.Style.FILL, 0); 155 | scaleStickPaint = creatPaint(scaleBallStickColor, 0, Paint.Style.FILL, 0); 156 | } 157 | 158 | /** 159 | * 初始化自定义属性 160 | * 161 | * @param context 上下文 162 | * @param attrs 自定义属性 163 | */ 164 | private void initAttrs(Context context, @Nullable AttributeSet attrs) { 165 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyElongScaleSeekBar, 0, R.style.default_scale_seekbar_style); 166 | int indexCount = typedArray.getIndexCount(); 167 | for (int i = 0; i < indexCount; i++) { 168 | int attr = typedArray.getIndex(i); 169 | switch (attr) { 170 | case R.styleable.MyElongScaleSeekBar_scale_progress_normal_color: 171 | scaleProgressNormalColor = typedArray.getColor(attr, Color.BLACK); 172 | break; 173 | case R.styleable.MyElongScaleSeekBar_scale_progress_section_color: 174 | scaleProgressSectionColor = typedArray.getColor(attr, Color.BLACK); 175 | break; 176 | case R.styleable.MyElongScaleSeekBar_scale_left_ball_bg_color: 177 | scaleLeftBallBgColor = typedArray.getColor(attr, Color.BLACK); 178 | break; 179 | case R.styleable.MyElongScaleSeekBar_scale_left_ball_stroke_color: 180 | scaleLeftBallStrokeColor = typedArray.getColor(attr, Color.BLACK); 181 | break; 182 | case R.styleable.MyElongScaleSeekBar_scale_left_ball_stroke_with: 183 | scaleLeftBallStrokeWith = typedArray.getDimensionPixelOffset(attr, 0); 184 | break; 185 | case R.styleable.MyElongScaleSeekBar_scale_right_ball_bg_color: 186 | scaleRightBallBgColor = typedArray.getColor(attr, Color.BLACK); 187 | break; 188 | case R.styleable.MyElongScaleSeekBar_scale_right_ball_stroke_color: 189 | scaleRightBallStrokeColor = typedArray.getColor(attr, Color.BLACK); 190 | break; 191 | case R.styleable.MyElongScaleSeekBar_scale_right_ball_stroke_with: 192 | scaleRightBallStrokeWith = typedArray.getDimensionPixelOffset(attr, 0); 193 | break; 194 | case R.styleable.MyElongScaleSeekBar_scale_ball_radio: 195 | scaleBallRadio = typedArray.getDimensionPixelOffset(attr, 0); 196 | break; 197 | case R.styleable.MyElongScaleSeekBar_scale_seek_height: 198 | scaleSeekHeight = typedArray.getDimensionPixelOffset(attr, 0); 199 | break; 200 | case R.styleable.MyElongScaleSeekBar_scale_left_text_color: 201 | scaleLeftTextColor = typedArray.getColor(attr, Color.BLACK); 202 | break; 203 | case R.styleable.MyElongScaleSeekBar_scale_left_text_size: 204 | scaleLeftTextSize = typedArray.getDimensionPixelOffset(attr, 0); 205 | break; 206 | case R.styleable.MyElongScaleSeekBar_scale_right_text_color: 207 | scaleRightTextColor = typedArray.getColor(attr, Color.BLACK); 208 | break; 209 | case R.styleable.MyElongScaleSeekBar_scale_right_text_size: 210 | scaleRightTextSize = typedArray.getDimensionPixelOffset(attr, 0); 211 | break; 212 | case R.styleable.MyElongScaleSeekBar_scale_text_margin_ball: 213 | scaleTextMarginBall = typedArray.getDimensionPixelOffset(attr, 0); 214 | break; 215 | case R.styleable.MyElongScaleSeekBar_scale_progress_unit: 216 | scaleProgressUnit = typedArray.getInteger(attr, 0); 217 | break; 218 | case R.styleable.MyElongScaleSeekBar_scale_ball_shadow_color: 219 | scaleBallShadowColor = typedArray.getColor(attr, Color.BLACK); 220 | break; 221 | case R.styleable.MyElongScaleSeekBar_scale_ball_shadow_radio: 222 | scaleBallShadowRadio = typedArray.getDimensionPixelOffset(attr, 0); 223 | break; 224 | case R.styleable.MyElongScaleSeekBar_scale_ball_stick_color: 225 | scaleBallStickColor = typedArray.getColor(attr, Color.BLACK); 226 | break; 227 | case R.styleable.MyElongScaleSeekBar_scale_ball_stick_height: 228 | scaleBallStickHeight = typedArray.getDimensionPixelOffset(attr, 0); 229 | break; 230 | case R.styleable.MyElongScaleSeekBar_scale_ball_stick_width: 231 | scaleBallStickWidth = typedArray.getDimensionPixelOffset(attr, 0); 232 | break; 233 | case R.styleable.MyElongScaleSeekBar_scale_ball_stick_margin: 234 | scaleBallStickMargin = typedArray.getDimensionPixelOffset(attr, 0); 235 | break; 236 | case R.styleable.MyElongScaleSeekBar_scale_symbol_front: 237 | symbolFront = typedArray.getString(attr); 238 | break; 239 | default: 240 | } 241 | } 242 | typedArray.recycle(); 243 | } 244 | 245 | @Override 246 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 247 | super.onSizeChanged(w, h, oldw, oldh); 248 | 249 | //球的半径包含描边 250 | radioWithStroke = scaleBallRadio + scaleLeftBallStrokeWith * 0.5F; 251 | xCoordinateUnit = radioWithStroke * 2; 252 | valueEntities = calculateAllXCoordinate(scaleProgressUnit, w - radioWithStroke * 2, maxValue); 253 | 254 | if (valueEntities != null && valueEntities.size() > 0) { 255 | currentLeft = valueEntities.get(0); 256 | currentRight = valueEntities.get(valueEntities.size() - 1); 257 | 258 | leftDesc = creatCurrentDataDesc(currentLeft); 259 | rightDesc = creatCurrentDataDesc(currentRight); 260 | 261 | if (this.seekBarDragListener != null) { 262 | this.seekBarDragListener.seekMoveValue(currentLeft.value, currentRight.value); 263 | } 264 | } 265 | 266 | if (currentLeft != null && currentRight != null) { 267 | // 背景进度条的区域 268 | scaleSeekNormalRectF = new RectF(); 269 | scaleSeekNormalRectF.left = 0; 270 | float top = h - (scaleBallRadio + scaleLeftBallStrokeWith * 0.5F) - scaleSeekHeight * 0.5F - scaleBallShadowRadio; 271 | scaleSeekNormalRectF.top = top; 272 | scaleSeekNormalRectF.bottom = top + scaleSeekHeight; 273 | scaleSeekNormalRectF.right = w; 274 | 275 | 276 | // 左边球的圆心坐标 277 | leftBallPoint = new SeekPoint(); 278 | leftBallPoint.x = currentLeft.xCoordinat + radioWithStroke; 279 | leftBallPoint.y = h - radioWithStroke - scaleBallShadowRadio; 280 | 281 | // 左边球中间的猴三棍 282 | calculateLeftSticks(); 283 | 284 | // 右边球的坐标 285 | rightBallPoint = new SeekPoint(); 286 | rightBallPoint.x = currentRight.xCoordinat + radioWithStroke; 287 | rightBallPoint.y = h - radioWithStroke - scaleBallShadowRadio; 288 | 289 | // 右边球中间的猴三棍 290 | calculateRightSticks(); 291 | 292 | // 选中背景条的间距部分 293 | scaleSeekSectionRectF = new RectF(); 294 | scaleSeekSectionRectF.left = leftBallPoint.x - radioWithStroke; 295 | scaleSeekSectionRectF.right = rightBallPoint.x - radioWithStroke; 296 | scaleSeekSectionRectF.top = scaleSeekNormalRectF.top; 297 | scaleSeekSectionRectF.bottom = scaleSeekNormalRectF.bottom; 298 | 299 | // 圆球的路径和区域 300 | scaleLeftBallPath = new Path(); 301 | scaleLeftBallPath.addCircle(leftBallPoint.x, leftBallPoint.y, radioWithStroke, Path.Direction.CW); 302 | 303 | scaleRightBallPath = new Path(); 304 | scaleRightBallPath.addCircle(rightBallPoint.x, rightBallPoint.y, radioWithStroke, Path.Direction.CW); 305 | 306 | scaleLeftBallRegion = updateRegionByPath(scaleLeftBallPath); 307 | scaleRightBallRegion = updateRegionByPath(scaleRightBallPath); 308 | } 309 | } 310 | 311 | /** 312 | * 右边球中间的猴三棍 313 | */ 314 | private void calculateRightSticks() { 315 | stickRightRectF2 = new RectF(); 316 | stickRightRectF2.left = rightBallPoint.x - scaleBallStickWidth * 0.5F; 317 | stickRightRectF2.right = rightBallPoint.x + scaleBallStickWidth * 0.5F; 318 | stickRightRectF2.top = rightBallPoint.y - scaleBallStickHeight * 0.5F; 319 | stickRightRectF2.bottom = rightBallPoint.y + scaleBallStickHeight * 0.5F; 320 | 321 | stickRightRectF1 = new RectF(); 322 | stickRightRectF1.left = stickRightRectF2.left - scaleBallStickMargin - scaleBallStickWidth; 323 | stickRightRectF1.right = stickRightRectF2.left - scaleBallStickMargin; 324 | stickRightRectF1.top = stickRightRectF2.top; 325 | stickRightRectF1.bottom = stickRightRectF2.bottom; 326 | 327 | stickRightRectF3 = new RectF(); 328 | stickRightRectF3.left = stickRightRectF2.right + scaleBallStickMargin; 329 | stickRightRectF3.right = stickRightRectF2.right + scaleBallStickMargin + scaleBallStickWidth; 330 | stickRightRectF3.top = stickRightRectF2.top; 331 | stickRightRectF3.bottom = stickRightRectF2.bottom; 332 | } 333 | 334 | /** 335 | * 绘制左边的猴三棍 336 | */ 337 | private void calculateLeftSticks() { 338 | stickLeftRectF2 = new RectF(); 339 | stickLeftRectF2.left = leftBallPoint.x - scaleBallStickWidth * 0.5F; 340 | stickLeftRectF2.right = leftBallPoint.x + scaleBallStickWidth * 0.5F; 341 | stickLeftRectF2.top = leftBallPoint.y - scaleBallStickHeight * 0.5F; 342 | stickLeftRectF2.bottom = leftBallPoint.y + scaleBallStickHeight * 0.5F; 343 | 344 | stickLeftRectF1 = new RectF(); 345 | stickLeftRectF1.left = stickLeftRectF2.left - scaleBallStickMargin - scaleBallStickWidth; 346 | stickLeftRectF1.right = stickLeftRectF2.left - scaleBallStickMargin; 347 | stickLeftRectF1.top = stickLeftRectF2.top; 348 | stickLeftRectF1.bottom = stickLeftRectF2.bottom; 349 | 350 | stickLeftRectF3 = new RectF(); 351 | stickLeftRectF3.left = stickLeftRectF2.right + scaleBallStickMargin; 352 | stickLeftRectF3.right = stickLeftRectF2.right + scaleBallStickMargin + scaleBallStickWidth; 353 | stickLeftRectF3.top = stickLeftRectF2.top; 354 | stickLeftRectF3.bottom = stickLeftRectF2.bottom; 355 | } 356 | 357 | /** 358 | * 变化的region 359 | * 360 | * @param path 路径 361 | * @return 变化的region 362 | */ 363 | private Region updateRegionByPath(Path path) { 364 | Region region = new Region(); 365 | if (path != null) { 366 | RectF tempRectF = new RectF(); 367 | path.computeBounds(tempRectF, true); 368 | region.setPath(path, new Region((int) tempRectF.left, (int) tempRectF.top, (int) tempRectF.right, (int) tempRectF.bottom)); 369 | } 370 | return region; 371 | } 372 | 373 | @Override 374 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 375 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 376 | int measureHeight; 377 | if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { 378 | measureHeight = Math.max(scaleLeftTextSize, scaleRightTextSize) + 2 + scaleTextMarginBall + scaleBallRadio * 2; 379 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY); 380 | } 381 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 382 | } 383 | 384 | @Override 385 | protected void onDraw(Canvas canvas) { 386 | super.onDraw(canvas); 387 | drawScaleSeekNormal(canvas); 388 | drawScaleSeekSection(canvas); 389 | drawLeftBall(canvas); 390 | drawRightBall(canvas); 391 | drawLeftText(canvas); 392 | drawRightText(canvas); 393 | } 394 | 395 | /** 396 | * 绘制右边的文字 397 | * 398 | * @param canvas 画布 399 | */ 400 | private void drawRightText(Canvas canvas) { 401 | SeekPoint measureTextSize = measureTextSize(scaleRightTextPaint, rightDesc); 402 | 403 | float textX; 404 | float textY; 405 | float incrementX; 406 | if (rightBallPoint.x - leftBallPoint.x <= xCoordinateUnit) { 407 | incrementX = radioWithStroke / 2; 408 | } else { 409 | incrementX = 0; 410 | } 411 | if (measureTextSize != null) { 412 | textX = rightBallPoint.x - measureTextSize.x / 2 + incrementX; 413 | } else { 414 | textX = rightBallPoint.x; 415 | } 416 | textY = rightBallPoint.y - radioWithStroke - scaleTextMarginBall; 417 | canvas.drawText(rightDesc, textX, textY, scaleRightTextPaint); 418 | } 419 | 420 | /** 421 | * 绘制左边的文字 422 | * 423 | * @param canvas 画布 424 | */ 425 | private void drawLeftText(Canvas canvas) { 426 | SeekPoint measureTextSize = measureTextSize(scaleLeftTextPaint, leftDesc); 427 | float textX; 428 | float textY; 429 | float incrementX; 430 | 431 | if (rightBallPoint.x - leftBallPoint.x <= xCoordinateUnit) { 432 | incrementX = radioWithStroke / 2; 433 | } else { 434 | incrementX = 0; 435 | } 436 | if (measureTextSize != null) { 437 | textX = leftBallPoint.x - measureTextSize.x / 2 - incrementX; 438 | } else { 439 | textX = leftBallPoint.x; 440 | } 441 | textY = leftBallPoint.y - radioWithStroke - scaleTextMarginBall; 442 | canvas.drawText(leftDesc, textX, textY, scaleLeftTextPaint); 443 | } 444 | 445 | /** 446 | * 绘制右边的滑块球 447 | * 448 | * @param canvas 画布 449 | */ 450 | private void drawRightBall(Canvas canvas) { 451 | scaleRightBallStrokePaint.setShadowLayer(scaleBallShadowRadio, 0, 2, scaleBallShadowColor); 452 | setLayerType(LAYER_TYPE_SOFTWARE, null); 453 | canvas.drawCircle(rightBallPoint.x, rightBallPoint.y, radioWithStroke, scaleRightBallStrokePaint); 454 | canvas.drawCircle(rightBallPoint.x, rightBallPoint.y, scaleBallRadio, scaleRightBallBgPaint); 455 | // 绘制右边边球猴三棍 456 | canvas.drawRoundRect(stickRightRectF1, scaleBallStickWidth * 0.5F, scaleBallStickWidth * 0.5F, scaleStickPaint); 457 | canvas.drawRoundRect(stickRightRectF2, scaleBallStickWidth * 0.5F, scaleBallStickWidth * 0.5F, scaleStickPaint); 458 | canvas.drawRoundRect(stickRightRectF3, scaleBallStickWidth * 0.5F, scaleBallStickWidth * 0.5F, scaleStickPaint); 459 | } 460 | 461 | /** 462 | * 绘制左边的滑块球 463 | * 464 | * @param canvas 画布 465 | */ 466 | private void drawLeftBall(Canvas canvas) { 467 | scaleLeftBallStrokePaint.setShadowLayer(scaleBallShadowRadio, 0, 2, scaleBallShadowColor); 468 | setLayerType(LAYER_TYPE_SOFTWARE, null); 469 | canvas.drawCircle(leftBallPoint.x, leftBallPoint.y, radioWithStroke, scaleLeftBallStrokePaint); 470 | canvas.drawCircle(leftBallPoint.x, leftBallPoint.y, scaleBallRadio, scaleLeftBallBgPaint); 471 | // 绘制左边球猴三棍 472 | canvas.drawRoundRect(stickLeftRectF1, scaleBallStickWidth * 0.5F, scaleBallStickWidth * 0.5F, scaleStickPaint); 473 | canvas.drawRoundRect(stickLeftRectF2, scaleBallStickWidth * 0.5F, scaleBallStickWidth * 0.5F, scaleStickPaint); 474 | canvas.drawRoundRect(stickLeftRectF3, scaleBallStickWidth * 0.5F, scaleBallStickWidth * 0.5F, scaleStickPaint); 475 | } 476 | 477 | /** 478 | * 绘制选中的进度条背景 479 | * 480 | * @param canvas 画布 481 | */ 482 | private void drawScaleSeekSection(Canvas canvas) { 483 | canvas.drawRect(scaleSeekSectionRectF, scaleProgressSectionPaint); 484 | } 485 | 486 | /** 487 | * 绘制进度条背景 488 | * 489 | * @param canvas 画布 490 | */ 491 | private void drawScaleSeekNormal(Canvas canvas) { 492 | canvas.drawRect(scaleSeekNormalRectF, scaleProgressNormalPaint); 493 | } 494 | 495 | 496 | private boolean leftChange; 497 | private boolean rightChange; 498 | 499 | @Override 500 | public boolean onTouchEvent(MotionEvent event) { 501 | 502 | switch (event.getAction()) { 503 | case MotionEvent.ACTION_DOWN: 504 | boolean touchLeftBall = scaleLeftBallRegion.contains((int) event.getX(), (int) event.getY()) && !scaleRightBallRegion.contains((int) event.getX(), (int) event.getY()); 505 | boolean touchRightBall = scaleRightBallRegion.contains((int) event.getX(), (int) event.getY()) && !scaleLeftBallRegion.contains((int) event.getX(), (int) event.getY()); 506 | 507 | //更新左边球的位置 508 | if (touchLeftBall) { 509 | leftChange = true; 510 | leftBallPoint.x = event.getX(); 511 | calculateLeftSticks(); 512 | } 513 | 514 | // 更新右边球的位置 515 | if (touchRightBall) { 516 | rightChange = true; 517 | rightBallPoint.x = event.getX(); 518 | calculateRightSticks(); 519 | } 520 | 521 | // 更新中间选中条的区域 522 | scaleSeekSectionRectF.left = leftBallPoint.x + radioWithStroke; 523 | scaleSeekSectionRectF.right = rightBallPoint.x + radioWithStroke; 524 | 525 | break; 526 | case MotionEvent.ACTION_MOVE: 527 | 528 | // 左边球正在移动 529 | if (leftChange) { 530 | // 限制左边界 531 | leftBallPoint.x = event.getX(); 532 | if (event.getX() < radioWithStroke) { 533 | leftBallPoint.x = radioWithStroke; 534 | } 535 | // 左边球右滑动的右边界 536 | // 两球最近的距离为单位步长的x距离轴 537 | if (rightBallPoint.x + radioWithStroke - event.getX() < xCoordinateUnit) { 538 | leftBallPoint.x = rightBallPoint.x + radioWithStroke - xCoordinateUnit; 539 | } 540 | 541 | calculateLeftSticks(); 542 | 543 | // 查找移动的时候坐标对应的数据实体 544 | int computation = (int) realTimeComputation(event.getX(), maxValue, getWidth()) < 0 ? 0 : (int) realTimeComputation(event.getX(), maxValue, getWidth()); 545 | String tempL = leftDesc; 546 | 547 | if (computation % scaleProgressUnit == 0) { 548 | leftDesc = symbolFront + computation; 549 | } else { 550 | leftDesc = tempL; 551 | } 552 | } 553 | 554 | // 右边球正在移动 555 | if (rightChange) { 556 | rightBallPoint.x = event.getX(); 557 | if (event.getX() > getWidth() - radioWithStroke) { 558 | rightBallPoint.x = getWidth() - radioWithStroke; 559 | } 560 | // 右边球滑动的左边界(两球最近的距离为单位步长的x距离轴) 561 | if (event.getX() - leftBallPoint.x - radioWithStroke < xCoordinateUnit) { 562 | rightBallPoint.x = leftBallPoint.x - radioWithStroke + xCoordinateUnit; 563 | } 564 | calculateRightSticks(); 565 | 566 | // 查找移动的时候坐标对应的数据实体 567 | int computation = (int) realTimeComputation(event.getX(), maxValue, getWidth()) > maxValue ? maxValue : (int) realTimeComputation(event.getX(), maxValue, getWidth()); 568 | String tempR = rightDesc; 569 | if (computation % scaleProgressUnit == 0) { 570 | rightDesc = symbolFront + computation; 571 | } else { 572 | rightDesc = tempR; 573 | } 574 | } 575 | 576 | // 更新中间选中条的区域 577 | scaleSeekSectionRectF.left = leftBallPoint.x + radioWithStroke; 578 | scaleSeekSectionRectF.right = rightBallPoint.x + radioWithStroke; 579 | 580 | break; 581 | case MotionEvent.ACTION_UP: 582 | Log.e(TAG, "rightBallPoint.x=" + rightBallPoint.x + "--->leftBallPoint.x=" + leftBallPoint.x + "--想减=" + (rightBallPoint.x - leftBallPoint.x) + "xCoordinateUnit=" + xCoordinateUnit); 583 | 584 | // 根据拖拽的单位步长,计算出最后回弹的正确位置 585 | if (leftChange) { 586 | // 限制左边界 587 | leftBallPoint.x = event.getX(); 588 | 589 | List temp = new ArrayList<>(); 590 | for (int i = 0; i <= currentRight.position; i++) { 591 | temp.add(valueEntities.get(i)); 592 | } 593 | // 如果之前不是0,但是没找到,那么赋值给他之前的元素 594 | UnitValueEntity tempPre = currentLeft; 595 | currentLeft = binarySearchKey(temp, (int) event.getX()); 596 | if (currentLeft.position == 0) { 597 | currentLeft = tempPre; 598 | } 599 | // 重合返回前一个 600 | if (currentLeft.position == currentRight.position) { 601 | currentLeft = tempPre; 602 | } 603 | 604 | // 左边临界点 605 | if (event.getX() < radioWithStroke) { 606 | leftBallPoint.x = radioWithStroke; 607 | currentLeft = binarySearchKey(valueEntities, (int) leftBallPoint.x); 608 | } 609 | 610 | 611 | if (event.getX() < valueEntities.get(1).xCoordinat - radioWithStroke) { 612 | currentLeft = valueEntities.get(0); 613 | } 614 | 615 | // 重新最终左边球抬起来的时候坐标 616 | if (currentLeft != null) { 617 | leftBallPoint.x = currentLeft.xCoordinat + radioWithStroke; 618 | scaleSeekSectionRectF.left = leftBallPoint.x + radioWithStroke; 619 | calculateLeftSticks(); 620 | } 621 | } 622 | 623 | if (rightChange) { 624 | // 左边球的抬起位置 625 | rightBallPoint.x = event.getX(); 626 | 627 | List temp = new ArrayList<>(); 628 | for (int i = currentLeft.position + 1; i < valueEntities.size(); i++) { 629 | temp.add(valueEntities.get(i)); 630 | } 631 | 632 | currentRight = binarySearchKey(temp, (int) event.getX()); 633 | 634 | // 右边界的限制坐标 635 | if (event.getX() > getWidth() - radioWithStroke) { 636 | rightBallPoint.x = getWidth() - radioWithStroke; 637 | currentRight = binarySearchKey(valueEntities, (int) rightBallPoint.x); 638 | } 639 | 640 | if (valueEntities.size() > 1) { 641 | if (event.getX() > valueEntities.get(valueEntities.size() - 2).xCoordinat) { 642 | // 超过倒数第二个直接赋值最后一个 643 | currentRight = valueEntities.get(valueEntities.size() - 1); 644 | } 645 | } 646 | 647 | // 最后确认右边球抬起的最终位置 648 | if (currentRight != null) { 649 | rightBallPoint.x = currentRight.xCoordinat + radioWithStroke; 650 | scaleSeekSectionRectF.right = rightBallPoint.x + radioWithStroke; 651 | calculateRightSticks(); 652 | } 653 | } 654 | 655 | // 更新两个球的最后path 656 | scaleLeftBallPath = new Path(); 657 | scaleLeftBallPath.addCircle(leftBallPoint.x, leftBallPoint.y, radioWithStroke, Path.Direction.CW); 658 | 659 | scaleRightBallPath = new Path(); 660 | scaleRightBallPath.addCircle(rightBallPoint.x, rightBallPoint.y, radioWithStroke, Path.Direction.CW); 661 | 662 | scaleLeftBallRegion = updateRegionByPath(scaleLeftBallPath); 663 | scaleRightBallRegion = updateRegionByPath(scaleRightBallPath); 664 | 665 | // 重置标记位 666 | leftChange = false; 667 | rightChange = false; 668 | 669 | // 抬起的时候确认要显示的文案 670 | leftDesc = creatCurrentDataDesc(currentLeft); 671 | rightDesc = creatCurrentDataDesc(currentRight); 672 | 673 | if (this.seekBarDragListener != null) { 674 | this.seekBarDragListener.seekMoveValue(currentLeft.value, currentRight.value); 675 | } 676 | break; 677 | default: 678 | break; 679 | } 680 | 681 | invalidate(); 682 | return true; 683 | } 684 | 685 | /** 686 | * 创建画笔 687 | * 688 | * @return 画笔 689 | */ 690 | private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) { 691 | Paint paint = new Paint(); 692 | paint.setColor(paintColor); 693 | paint.setAntiAlias(true); 694 | paint.setStrokeWidth(lineWidth); 695 | paint.setDither(true); 696 | paint.setTextSize(textSize); 697 | paint.setStyle(style); 698 | paint.setStrokeCap(Paint.Cap.ROUND); 699 | paint.setStrokeJoin(Paint.Join.ROUND); 700 | return paint; 701 | } 702 | 703 | /** 704 | * 坐标对象 705 | */ 706 | class SeekPoint { 707 | public float x; 708 | public float y; 709 | 710 | SeekPoint() { 711 | } 712 | 713 | public SeekPoint(float x, float y) { 714 | this.x = x; 715 | this.y = y; 716 | } 717 | } 718 | 719 | 720 | /** 721 | * 计算单位坐标下对应的具体的x坐标和对应的value 722 | * 723 | * @param scaleProgressUnit 单位 724 | * @param seekWidth 最大长度(去除两个半径的长度,因为最后一个球的终点是按照最左边来计算的) 725 | * @param maxValue 最大值 726 | * @return 返回对应坐标段数实体的集合 727 | */ 728 | private List calculateAllXCoordinate(int scaleProgressUnit, float seekWidth, int maxValue) { 729 | List xValueEntitys = new ArrayList<>(); 730 | //先判断是否有剩余不足一段的数据 731 | boolean hasLastOne = maxValue % scaleProgressUnit != 0; 732 | // 有的话,总数加1,没有的话,真好能除尽 733 | int count = hasLastOne ? maxValue / scaleProgressUnit + 1 : maxValue / scaleProgressUnit; 734 | // 计算各个点,对应生产的实体坐标 735 | for (int i = 0; i <= count; i++) { 736 | UnitValueEntity temp = new UnitValueEntity(); 737 | temp.position = i; 738 | 739 | if (i != count) { 740 | temp.value = i * scaleProgressUnit; 741 | temp.xCoordinat = (seekWidth / maxValue) * i * scaleProgressUnit; 742 | } else { 743 | temp.value = maxValue; 744 | temp.xCoordinat = seekWidth; 745 | } 746 | 747 | xValueEntitys.add(temp); 748 | } 749 | return xValueEntitys; 750 | } 751 | 752 | /** 753 | * 根据传入的单位长度定义的实体对象 754 | */ 755 | class UnitValueEntity implements Comparable { 756 | public int position; 757 | public float xCoordinat; 758 | public int value; 759 | 760 | 761 | @Override 762 | public int compareTo(@NonNull UnitValueEntity o) { 763 | return (int) (this.xCoordinat - o.xCoordinat); 764 | } 765 | 766 | @Override 767 | public String toString() { 768 | return "UnitValueEntity{" + 769 | "position=" + position + 770 | ", xCoordinat=" + xCoordinat + 771 | ", value=" + value + 772 | '}'; 773 | } 774 | } 775 | 776 | /** 777 | * 查找最近接的数据点 778 | * 779 | * @param data 源数据集合 780 | * @param targetNum 当前的x轴的坐标 781 | * @return 当前点对应的实体 782 | */ 783 | public UnitValueEntity binarySearchKey(List data, int targetNum) { 784 | if (data != null && data.size() > 0) { 785 | int left = 0, right = 0; 786 | for (right = data.size() - 1; left != right; ) { 787 | int midIndex = (right + left) / 2; 788 | int mid = (right - left); 789 | int midValue = (int) data.get(midIndex).xCoordinat; 790 | if (targetNum == midValue) { 791 | return data.get(midIndex); 792 | } 793 | if (targetNum > midValue) { 794 | left = midIndex; 795 | } else { 796 | right = midIndex; 797 | } 798 | 799 | if (mid <= 1) { 800 | break; 801 | } 802 | } 803 | UnitValueEntity rightnum = data.get(right); 804 | UnitValueEntity leftnum = data.get(left); 805 | return Math.abs((rightnum.xCoordinat - leftnum.xCoordinat) / 2) > Math.abs(rightnum.xCoordinat - targetNum) ? rightnum : leftnum; 806 | } 807 | return null; 808 | } 809 | 810 | /** 811 | * 返回文字的宽和高,x代表宽,y代表高度 812 | * 813 | * @param textPaint 画笔 814 | * @param text 文字 815 | * @return 文字的宽和高 816 | */ 817 | private SeekPoint measureTextSize(Paint textPaint, String text) { 818 | SeekPoint point = new SeekPoint(); 819 | if (!TextUtils.isEmpty(text)) { 820 | point.x = textPaint.measureText(text); 821 | Paint.FontMetrics fm = textPaint.getFontMetrics(); 822 | point.y = (float) Math.ceil(fm.descent - fm.top); 823 | } 824 | return point; 825 | } 826 | 827 | /** 828 | * 当前对应坐标下的文案描述 829 | * 830 | * @param unitValueEntity 对应的坐标数据实体 831 | * @return 返回对应的文案 832 | */ 833 | private String creatCurrentDataDesc(UnitValueEntity unitValueEntity) { 834 | return unitValueEntity == null ? symbolFront : symbolFront + unitValueEntity.value; 835 | } 836 | 837 | /** 838 | * 实时计算当前坐标对应的value值 839 | * 840 | * @param currentX 当前x坐标 841 | * @param maxValue 最大值 842 | * @param viewWidth view宽度 843 | * @return 当前值 844 | */ 845 | private float realTimeComputation(float currentX, int maxValue, float viewWidth) { 846 | return maxValue * currentX / viewWidth; 847 | } 848 | 849 | /** 850 | * 透传给外界的值 851 | */ 852 | public interface OnSeekBarDragListener { 853 | void seekMoveValue(int left, int right); 854 | } 855 | 856 | @Override 857 | protected void onWindowVisibilityChanged(int visibility) { 858 | super.onWindowVisibilityChanged(visibility); 859 | invalidate(); 860 | } 861 | } 862 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/customview/MyElongVerticalTextView.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.customview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.TextView; 6 | 7 | /** 8 | * Created by user on 16/9/19. 9 | */ 10 | 11 | public class MyElongVerticalTextView extends TextView { 12 | 13 | public MyElongVerticalTextView(Context context) { 14 | super(context); 15 | } 16 | 17 | public MyElongVerticalTextView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public MyElongVerticalTextView(Context context, AttributeSet attrs, int defStyle) { 22 | super(context, attrs, defStyle); 23 | } 24 | 25 | @Override 26 | public void setText(CharSequence text, BufferType type) { 27 | String verticalText = generateVerticalText(text.toString()); 28 | super.setText(verticalText, type); 29 | } 30 | 31 | private String generateVerticalText(String desc) { 32 | StringBuilder stringBuffer = new StringBuilder(); 33 | char[] chars = desc.toCharArray(); 34 | for (char currentChar : chars) { 35 | String temp = currentChar + "\n"; 36 | stringBuffer.append(temp); 37 | } 38 | return stringBuffer.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/customview/SwitchView.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.customview; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.RectF; 9 | import android.util.AttributeSet; 10 | import android.util.TypedValue; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | import com.asiatravel.atdragviewdemo.R; 15 | 16 | /** 17 | * Created by Jsion on 16/9/8. 18 | */ 19 | 20 | public class SwitchView extends View { 21 | private static final int DEF_H = 60; 22 | private static final int DEF_W = 120; 23 | 24 | private int switchViewBgCloseColor; 25 | private int switchViewBgOpenColor; 26 | private int switchViewStrockColor; 27 | private int switchViewBallColor; 28 | private int switchViewStrockWidth; 29 | 30 | private Paint viewBgPaint; 31 | private Paint viewStrockPaint; 32 | private Paint viewBallPaint; 33 | 34 | private int viewHeight; 35 | private int viewWidth; 36 | private int strockRadio; 37 | private int solidRadio; 38 | 39 | private RectF bgRectF, bgStrockRectF; 40 | private boolean isOpen; 41 | private float swichBallx; 42 | 43 | public SwitchView(Context context) { 44 | this(context, null); 45 | } 46 | 47 | public SwitchView(Context context, AttributeSet attrs) { 48 | this(context, attrs, 0); 49 | } 50 | 51 | public SwitchView(Context context, AttributeSet attrs, int defStyleAttr) { 52 | super(context, attrs, defStyleAttr); 53 | TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SwitchView, defStyleAttr, R.style.def_switch_view); 54 | int indexCount = typedArray.getIndexCount(); 55 | for (int i = 0; i < indexCount; i++) { 56 | int attr = typedArray.getIndex(i); 57 | switch (attr) { 58 | case R.styleable.SwitchView_switch_bg_close_color: 59 | switchViewBgCloseColor = typedArray.getColor(attr, Color.BLACK); 60 | break; 61 | case R.styleable.SwitchView_switch_bg_open_color: 62 | switchViewBgOpenColor = typedArray.getColor(attr, Color.BLACK); 63 | break; 64 | case R.styleable.SwitchView_switch_bg_strock_width: 65 | switchViewStrockWidth = typedArray.getDimensionPixelOffset(attr, 0); 66 | break; 67 | case R.styleable.SwitchView_switch_strock_color: 68 | switchViewStrockColor = typedArray.getColor(attr, Color.BLACK); 69 | break; 70 | case R.styleable.SwitchView_switch_ball_color: 71 | switchViewBallColor = typedArray.getColor(attr, Color.BLACK); 72 | break; 73 | } 74 | } 75 | typedArray.recycle(); 76 | initData(); 77 | } 78 | 79 | private void initData() { 80 | viewBgPaint = creatPaint(switchViewBgCloseColor, 0, Paint.Style.FILL, 0); 81 | viewBallPaint = creatPaint(switchViewBallColor, 0, Paint.Style.FILL, 0); 82 | viewStrockPaint = creatPaint(switchViewStrockColor, 0, Paint.Style.FILL, 0); 83 | } 84 | 85 | @Override 86 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 87 | super.onSizeChanged(w, h, oldw, oldh); 88 | viewHeight = h; 89 | viewWidth = w; 90 | 91 | strockRadio = viewHeight / 2; 92 | solidRadio = (viewHeight - 2 * switchViewStrockWidth) / 2; 93 | 94 | swichBallx = strockRadio; 95 | bgStrockRectF = new RectF(0, 0, viewWidth, viewHeight); 96 | bgRectF = new RectF(switchViewStrockWidth, switchViewStrockWidth, viewWidth - switchViewStrockWidth, viewHeight - switchViewStrockWidth); 97 | } 98 | 99 | @Override 100 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 101 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 102 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 103 | 104 | int measureWidth; 105 | int measureHeight; 106 | 107 | if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) { 108 | measureWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_W, getResources().getDisplayMetrics()); 109 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(measureWidth, MeasureSpec.EXACTLY); 110 | } 111 | 112 | if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { 113 | measureHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_H, getResources().getDisplayMetrics()); 114 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY); 115 | } 116 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 117 | } 118 | 119 | @Override 120 | protected void onDraw(Canvas canvas) { 121 | super.onDraw(canvas); 122 | drawSwichBg(canvas); 123 | drawSwichBallByFlag(canvas); 124 | } 125 | 126 | private void drawSwichBallByFlag(Canvas canvas) { 127 | canvas.drawCircle(swichBallx, strockRadio, solidRadio, viewBallPaint); 128 | } 129 | 130 | private void drawSwichBg(Canvas canvas) { 131 | canvas.drawRoundRect(bgStrockRectF, strockRadio, strockRadio, viewStrockPaint); 132 | canvas.drawRoundRect(bgRectF, solidRadio, solidRadio, viewBgPaint); 133 | } 134 | 135 | @Override 136 | public boolean onTouchEvent(MotionEvent event) { 137 | switch (event.getAction()) { 138 | case MotionEvent.ACTION_DOWN: 139 | break; 140 | case MotionEvent.ACTION_MOVE: 141 | int moveX = (int) event.getX(); 142 | if (moveX < strockRadio) { 143 | moveX = strockRadio; 144 | } 145 | if (moveX > viewWidth - strockRadio) { 146 | moveX = viewWidth - strockRadio; 147 | } 148 | swichBallx = moveX; 149 | break; 150 | case MotionEvent.ACTION_UP: 151 | int upX = (int) event.getX(); 152 | if (upX > viewWidth / 2) { 153 | setOpenState(true); 154 | } else { 155 | setOpenState(false); 156 | } 157 | break; 158 | } 159 | invalidate(); 160 | return true; 161 | } 162 | 163 | private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) { 164 | Paint paint = new Paint(); 165 | paint.setColor(paintColor); 166 | paint.setAntiAlias(true); 167 | paint.setStrokeWidth(lineWidth); 168 | paint.setDither(true); 169 | paint.setTextSize(textSize); 170 | paint.setStyle(style); 171 | paint.setStrokeCap(Paint.Cap.ROUND); 172 | paint.setStrokeJoin(Paint.Join.ROUND); 173 | return paint; 174 | } 175 | 176 | public void setOpenState(boolean openState) { 177 | isOpen = openState; 178 | swichBallx = isOpen ? viewWidth - strockRadio : strockRadio; 179 | int currentStrockColor = isOpen ? switchViewBgOpenColor : switchViewStrockColor; 180 | int currentBgColor = isOpen ? switchViewBgOpenColor : switchViewBgCloseColor; 181 | viewStrockPaint.setColor(currentStrockColor); 182 | viewBgPaint.setColor(currentBgColor); 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/ui/ListActivity.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.ui; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.AdapterView; 11 | import android.widget.BaseAdapter; 12 | import android.widget.ListView; 13 | import android.widget.ScrollView; 14 | import android.widget.TextView; 15 | 16 | import com.asiatravel.atdragviewdemo.R; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * Created by user on 16/9/21. 22 | */ 23 | 24 | public class ListActivity extends AppCompatActivity { 25 | @Override 26 | protected void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_list); 29 | ListAdapter listAdapter = new ListAdapter(); 30 | ListView listView = (ListView) findViewById(R.id.lv_test); 31 | listView.setAdapter(listAdapter); 32 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 33 | @Override 34 | public void onItemClick(AdapterView parent, View view, int position, long id) { 35 | Intent intent = new Intent(ListActivity.this, ViewShowActivity.class); 36 | startActivity(intent); 37 | } 38 | }); 39 | } 40 | 41 | private String testALV(String data){ 42 | return ""; 43 | } 44 | 45 | class ListAdapter extends BaseAdapter { 46 | 47 | @Override 48 | public int getCount() { 49 | return 20; 50 | } 51 | 52 | @Override 53 | public Object getItem(int position) { 54 | return null; 55 | } 56 | 57 | @Override 58 | public long getItemId(int position) { 59 | return 0; 60 | } 61 | 62 | @Override 63 | public View getView(int position, View convertView, ViewGroup parent) { 64 | convertView = LayoutInflater.from(ListActivity.this).inflate(R.layout.layout_test, null, false); 65 | return convertView; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.ui; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.design.widget.FloatingActionButton; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.View; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.asiatravel.atdragviewdemo.R; 16 | import com.asiatravel.atdragviewdemo.customview.ATDragView; 17 | import com.asiatravel.atdragviewdemo.customview.CountDownCircleView; 18 | import com.asiatravel.atdragviewdemo.customview.MyElongVerticalTextView; 19 | import com.asiatravel.atdragviewdemo.customview.SwitchView; 20 | import com.orhanobut.logger.Logger; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | public class MainActivity extends AppCompatActivity { 26 | 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_main); 32 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 33 | setSupportActionBar(toolbar); 34 | 35 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 36 | fab.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View view) { 39 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 40 | .setAction("Action", null).show(); 41 | } 42 | }); 43 | 44 | 45 | test(); 46 | 47 | List data = new ArrayList<>(); 48 | data.add("0元"); 49 | data.add("400元"); 50 | data.add("800元"); 51 | data.add("1600元"); 52 | data.add("无限"); 53 | final CountDownCircleView countDownCircleView = (CountDownCircleView) findViewById(R.id.countdown_circle_view); 54 | 55 | 56 | ATDragView atDragView = (ATDragView) findViewById(R.id.at_dragView); 57 | atDragView.setData(data, new ATDragView.OnDragFinishedListener() { 58 | @Override 59 | public void dragFinished(int leftPostion, int rightPostion) { 60 | Toast.makeText(MainActivity.this, "回调数据Left-->" + leftPostion + "--Right-->" + rightPostion, Toast.LENGTH_SHORT).show(); 61 | 62 | countDownCircleView.startCountDown(new CountDownCircleView.OnCountDownFinishedListener() { 63 | @Override 64 | public void countDownStop() { 65 | Toast.makeText(MainActivity.this, "计时结束", Toast.LENGTH_LONG).show(); 66 | } 67 | }); 68 | 69 | } 70 | }); 71 | 72 | SwitchView switchView1 = (SwitchView) findViewById(R.id.switch_view1); 73 | SwitchView switchView2 = (SwitchView) findViewById(R.id.switch_view2); 74 | 75 | switchView1.setOpenState(false); 76 | switchView2.setOpenState(true); 77 | 78 | MyElongVerticalTextView verticalTextView = (MyElongVerticalTextView) findViewById(R.id.vt_textView); 79 | // verticalTextView.setText("QQ专属"); 80 | 81 | TextView jump = (TextView) findViewById(R.id.tv_test); 82 | jump.setOnClickListener(new View.OnClickListener() { 83 | @Override 84 | public void onClick(View v) { 85 | Intent intent = new Intent(MainActivity.this,ListActivity.class); 86 | startActivity(intent); 87 | } 88 | }); 89 | 90 | } 91 | 92 | @Override 93 | public boolean onCreateOptionsMenu(Menu menu) { 94 | // Inflate the menu; this adds items to the action bar if it is present. 95 | getMenuInflater().inflate(R.menu.menu_main, menu); 96 | return true; 97 | } 98 | 99 | @Override 100 | public boolean onOptionsItemSelected(MenuItem item) { 101 | // Handle action bar item clicks here. The action bar will 102 | // automatically handle clicks on the Home/Up button, so long 103 | // as you specify a parent activity in AndroidManifest.xml. 104 | int id = item.getItemId(); 105 | 106 | //noinspection SimplifiableIfStatement 107 | if (id == R.id.action_settings) { 108 | return true; 109 | } 110 | 111 | return super.onOptionsItemSelected(item); 112 | } 113 | 114 | private void test() { 115 | for (int a = 0, b = 0; a < 2; b = ++a) { 116 | Logger.e("a===" + a + "---b=" + b); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/asiatravel/atdragviewdemo/ui/ViewShowActivity.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo.ui; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.DisplayMetrics; 11 | import android.view.View; 12 | import android.view.WindowManager; 13 | import android.widget.ScrollView; 14 | import android.widget.TextView; 15 | 16 | import com.asiatravel.atdragviewdemo.R; 17 | 18 | /** 19 | * Created by Jsion on 16/10/30. 20 | */ 21 | 22 | public class ViewShowActivity extends AppCompatActivity { 23 | private ScrollView scrollView; 24 | private TextView textView; 25 | private TextView next; 26 | private int index; 27 | 28 | @Override 29 | protected void onCreate(@Nullable Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_viewshow); 32 | 33 | scrollView = (ScrollView) findViewById(R.id.sv_container); 34 | textView = (TextView) findViewById(R.id.tv_content); 35 | next = (TextView) findViewById(R.id.tv_next); 36 | 37 | next.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | showNext(); 41 | } 42 | }); 43 | 44 | } 45 | 46 | private void showNext() { 47 | AnimatorSet animatorSetHide = new AnimatorSet(); 48 | final ObjectAnimator transHide = ObjectAnimator.ofFloat(scrollView, "translationX", 0, -getScreenWidth()); 49 | // final ObjectAnimator transHide = ObjectAnimator.ofFloat(scrollView, "translationX", 0, getScreenWidth()); 50 | ObjectAnimator alphHide = ObjectAnimator.ofFloat(scrollView, "alpha", 1.F, 0.F); 51 | animatorSetHide.setDuration(500); 52 | animatorSetHide.playTogether(transHide, alphHide); 53 | animatorSetHide.start(); 54 | animatorSetHide.addListener(new AnimatorListenerAdapter() { 55 | @Override 56 | public void onAnimationEnd(Animator animation) { 57 | super.onAnimationEnd(animation); 58 | changeContent(true); 59 | showContent(); 60 | } 61 | }); 62 | 63 | } 64 | 65 | private void showContent() { 66 | AnimatorSet animatorSetShow = new AnimatorSet(); 67 | ObjectAnimator transShow = ObjectAnimator.ofFloat(scrollView, "translationX", getScreenWidth(), 0); 68 | // ObjectAnimator transShow = ObjectAnimator.ofFloat(scrollView, "translationX", -getScreenWidth(), 0); 69 | ObjectAnimator alphShow = ObjectAnimator.ofFloat(scrollView, "alpha", 0F, 1F); 70 | animatorSetShow.playTogether(transShow, alphShow); 71 | animatorSetShow.setDuration(500); 72 | animatorSetShow.start(); 73 | } 74 | 75 | private int getScreenWidth() { 76 | WindowManager manager = this.getWindowManager(); 77 | DisplayMetrics outMetrics = new DisplayMetrics(); 78 | manager.getDefaultDisplay().getMetrics(outMetrics); 79 | return outMetrics.widthPixels; 80 | } 81 | 82 | private void changeContent(boolean showOrHide) { 83 | if (showOrHide) { 84 | index++; 85 | } 86 | textView.setText("我是下一题目---------" + index); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 19 | 20 | 25 | 26 | 37 | 38 | 39 | 40 | 50 | 51 | 52 | 53 | 62 | 63 | 64 | 65 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_viewshow.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 21 | 22 | 26 | 27 | 35 | 36 | 45 | 46 | 55 | 56 | 65 | 66 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 91 | 92 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 18 | 19 | 26 | 27 | 36 | 37 | 43 | 44 | 45 | 54 | 55 | 64 | 65 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 21 | 22 | 29 | 30 | 31 | 38 | 39 | 47 | 48 | 55 | 56 | 57 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 87 | 88 | 94 | 95 | 100 | 101 | 102 | 110 | 111 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/customer_service_flight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/app/src/main/res/mipmap-xhdpi/customer_service_flight.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/myelong_red_expired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/app/src/main/res/mipmap-xxhdpi/myelong_red_expired.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/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/attrs.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 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #E0E0E0 8 | #4499FF 9 | #FFFFFF 10 | #E6E6E6 11 | #888888 12 | #E7E7E7 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 1px 6 | 10dp 7 | 1dp 8 | 2dp 9 | 12sp 10 | 4dp 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ATDragViewDemo 3 | Settings 4 | 了快速沙发上来看设计方老师讲法律就是福建省连放假了手机费老师讲法律上激发了手机发送了房间里是否减肥了时间了就是就是浪费了时间粉丝及福利时间粉丝及法律就是房价是否就是垃圾分类手机费老师深刻理解是甲方设计方老师就睡觉了睡觉了附件是浪费时间发送垃圾分类是解放了手机费老师讲法律是减肥就是了房间了设计方老师就放了手机 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 28 | 29 | 37 | 38 | 45 | 46 | 47 | 58 | 59 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/src/test/java/com/asiatravel/atdragviewdemo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.asiatravel.atdragviewdemo; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /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.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuoFeilong/ATDragViewDemo/50c38bd2dacce27fca07751362a8d115e2bf3587/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.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 | # 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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------