├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── arts ├── rendering_1.gif ├── rendering_2.gif ├── rendering_3.gif ├── rendering_4.gif ├── rendering_5.gif └── rendering_6.gif ├── build.gradle ├── example ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── sevenheaven │ │ └── uilibrarysample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── sevenheaven │ │ │ └── uilibrarysample │ │ │ ├── ExampleDetailActivity.java │ │ │ ├── ExampleListActivity.java │ │ │ └── ExampleListAdapter.java │ └── res │ │ ├── drawable-nodpi │ │ ├── test0.png │ │ ├── test1.png │ │ └── test2.png │ │ ├── layout │ │ ├── activity_example_detail.xml │ │ ├── activity_example_list.xml │ │ ├── activity_main.xml │ │ └── item_examplelist.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── sevenheaven │ └── uilibrarysample │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── uilibrary ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── sevenheaven │ └── uilibrary │ └── ApplicationTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── sevenheaven │ │ └── uilibrary │ │ ├── drawables │ │ ├── GroupedAvatarDrawable.java │ │ ├── MaskDrawable.java │ │ ├── RoundEdgeDrawable.java │ │ └── progressive │ │ │ ├── LoadingDrawable.java │ │ │ ├── ProgressiveDrawable.java │ │ │ └── providers │ │ │ ├── AppStoreStyleProgressProvider.java │ │ │ └── PathProgressProvider.java │ │ ├── shapes │ │ ├── ArcShape.java │ │ └── PolygonShape.java │ │ ├── utils │ │ ├── GeomUtil.java │ │ └── PathMeasurement.java │ │ └── views │ │ ├── AnimatedImageView.java │ │ ├── FitWidthImageView.java │ │ ├── GroupedAvatarImageView.java │ │ └── PageIndicator.java └── res │ └── values │ ├── attrs.xml │ └── strings.xml └── test └── java └── com └── sevenheaven └── uilibrary └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Android 39 | 40 | 41 | Android > Lint > Correctness 42 | 43 | 44 | Android > Lint > Internationalization 45 | 46 | 47 | Android > Lint > Security 48 | 49 | 50 | CorrectnessLintAndroid 51 | 52 | 53 | Gradle 54 | 55 | 56 | LintAndroid 57 | 58 | 59 | Probable bugsGradle 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 81 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 7heaven 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](http://img.shields.io/:license-mit-blue.svg)](LICENSE) 2 | # UILibrary 3 | 4 | --- 5 | 6 | 把平时写的用到项目里面的一些UI相关的View、Drawable等整理了一下,持续更新 7 | 8 | 主要有以下一些效果: 9 | 10 | **跟着Path运动的ProgressBar效果** 11 | 12 | ![PathProgressProvider+ProgressiveDrawable](./arts/rendering_1.gif) 13 | 14 | **QQ群头像效果的控件** 15 | 16 | ![GroupedAvatarDrawable](./arts/rendering_2.gif) 17 | 18 | **苹果app下载的进度效果** 19 | 20 | ![AppStoreStyleProgressProvider+ProgressiveDrawable](./arts/rendering_3.gif) 21 | 22 | **任意形状的遮罩** 23 | 24 | ![MaskDrawable+PolygonShape](./arts/rendering_4.gif) 25 | 26 | **带圆角的任意正多边形和星型** 27 | 28 | ![PolygonShape](./arts/rendering_5.gif) 29 | 30 | **带动画效果切换ScaleType的ImageView** 31 | 32 | ![AnimatedImageView](./arts/rendering_6.gif) 33 | 34 | -------------------------------------------------------------------------------- /arts/rendering_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/arts/rendering_1.gif -------------------------------------------------------------------------------- /arts/rendering_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/arts/rendering_2.gif -------------------------------------------------------------------------------- /arts/rendering_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/arts/rendering_3.gif -------------------------------------------------------------------------------- /arts/rendering_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/arts/rendering_4.gif -------------------------------------------------------------------------------- /arts/rendering_5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/arts/rendering_5.gif -------------------------------------------------------------------------------- /arts/rendering_6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/arts/rendering_6.gif -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.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 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.sevenheaven.uilibrarysample" 9 | minSdkVersion 11 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:24.2.0' 25 | compile 'com.android.support:recyclerview-v7:24.2.0' 26 | 27 | compile project(':uilibrary') 28 | } 29 | -------------------------------------------------------------------------------- /example/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/7heaven/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 | -------------------------------------------------------------------------------- /example/src/androidTest/java/com/sevenheaven/uilibrarysample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrarysample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /example/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/src/main/java/com/sevenheaven/uilibrarysample/ExampleDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrarysample; 2 | 3 | import android.app.Activity; 4 | import android.graphics.drawable.Drawable; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | import android.widget.SeekBar; 12 | 13 | /** 14 | * Created by 7heaven on 16/8/25. 15 | */ 16 | public class ExampleDetailActivity extends Activity { 17 | 18 | public interface DetailContentProvider{ 19 | T provideInstance(); 20 | 21 | void onGestureMove(float x, float y, int action); 22 | 23 | void destroy(); 24 | } 25 | 26 | public static DetailContentProvider mContentProvider; 27 | 28 | private FrameLayout mRootLayout; 29 | 30 | private SeekBar xAxisSeekBar; 31 | private SeekBar yAxisSeekBar; 32 | 33 | private int mScreenWidth; 34 | private int mScreenHeight; 35 | public static final int PROGRESS_INIT = -2; 36 | public static final int PROGRESS_DONE = -1; 37 | 38 | public static final String PROVIDER_EXTRA = ""; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState){ 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_example_detail); 44 | 45 | mScreenWidth = getResources().getDisplayMetrics().widthPixels; 46 | mScreenHeight = getResources().getDisplayMetrics().heightPixels; 47 | 48 | mRootLayout = (FrameLayout) findViewById(R.id.root_layout); 49 | xAxisSeekBar = (SeekBar) findViewById(R.id.x_axis_seekbar); 50 | yAxisSeekBar = (SeekBar) findViewById(R.id.y_axis_seekbar); 51 | 52 | if(mContentProvider != null){ 53 | Object providedInstance = mContentProvider.provideInstance(); 54 | 55 | if(providedInstance instanceof View){ 56 | View instance = (View) providedInstance; 57 | ViewGroup.LayoutParams params = instance.getLayoutParams(); 58 | if(params == null){ 59 | params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 60 | instance.setLayoutParams(params); 61 | } 62 | 63 | mRootLayout.addView(instance); 64 | 65 | }else if(providedInstance instanceof Drawable){ 66 | Drawable instance = (Drawable) providedInstance; 67 | 68 | View view = new View(this); 69 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 70 | 71 | if(Build.VERSION.SDK_INT > 16){ 72 | view.setBackground(instance); 73 | }else{ 74 | view.setBackgroundDrawable(instance); 75 | } 76 | 77 | mRootLayout.addView(view); 78 | } 79 | 80 | xAxisSeekBar.setMax(100); 81 | yAxisSeekBar.setMax(100); 82 | 83 | xAxisSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 84 | @Override 85 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 86 | mContentProvider.onGestureMove((float) progress / 100.0F, (float) yAxisSeekBar.getProgress() / 100.0F, MotionEvent.ACTION_MOVE); 87 | } 88 | 89 | @Override 90 | public void onStartTrackingTouch(SeekBar seekBar) { 91 | mContentProvider.onGestureMove(PROGRESS_INIT, PROGRESS_INIT, MotionEvent.ACTION_DOWN); 92 | mContentProvider.onGestureMove(0, (float) yAxisSeekBar.getProgress() / 100.0F, MotionEvent.ACTION_DOWN); 93 | } 94 | 95 | @Override 96 | public void onStopTrackingTouch(SeekBar seekBar) { 97 | mContentProvider.onGestureMove(PROGRESS_DONE, PROGRESS_DONE, MotionEvent.ACTION_UP); 98 | } 99 | }); 100 | 101 | yAxisSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 102 | @Override 103 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 104 | mContentProvider.onGestureMove((float) xAxisSeekBar.getProgress() / 100.0F, (float) progress / 100.0F, MotionEvent.ACTION_MOVE); 105 | } 106 | 107 | @Override 108 | public void onStartTrackingTouch(SeekBar seekBar) { 109 | mContentProvider.onGestureMove(PROGRESS_INIT, PROGRESS_INIT, MotionEvent.ACTION_DOWN); 110 | mContentProvider.onGestureMove((float) xAxisSeekBar.getProgress() / 100.0F, 0, MotionEvent.ACTION_DOWN); 111 | } 112 | 113 | @Override 114 | public void onStopTrackingTouch(SeekBar seekBar) { 115 | mContentProvider.onGestureMove(PROGRESS_DONE, PROGRESS_DONE, MotionEvent.ACTION_UP); 116 | } 117 | }); 118 | } 119 | } 120 | 121 | // @Override 122 | // public boolean onTouchEvent(MotionEvent event){ 123 | // if(mContentProvider != null) { 124 | // int action = event.getActionMasked(); 125 | // switch (action) { 126 | // case MotionEvent.ACTION_DOWN: 127 | // mContentProvider.onGestureMove(PROGRESS_INIT, PROGRESS_INIT, action); 128 | // mContentProvider.onGestureMove(0, 0, action); 129 | // break; 130 | // case MotionEvent.ACTION_MOVE: 131 | // mContentProvider.onGestureMove(event.getX() / mScreenWidth, event.getY() / mScreenHeight, action); 132 | // break; 133 | // case MotionEvent.ACTION_UP: 134 | // mContentProvider.onGestureMove(PROGRESS_DONE, PROGRESS_DONE, action); 135 | // break; 136 | // } 137 | // return true; 138 | // }else{ 139 | // return super.onTouchEvent(event); 140 | // } 141 | // } 142 | 143 | @Override 144 | protected void onDestroy(){ 145 | super.onDestroy(); 146 | if(mRootLayout.getChildCount() > 0){ 147 | mRootLayout.removeAllViews(); 148 | } 149 | if(mContentProvider != null){ 150 | mContentProvider.destroy(); 151 | mContentProvider = null; 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /example/src/main/java/com/sevenheaven/uilibrarysample/ExampleListActivity.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrarysample; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.Rect; 12 | import android.graphics.RectF; 13 | import android.graphics.drawable.Drawable; 14 | import android.graphics.drawable.LayerDrawable; 15 | import android.graphics.drawable.ShapeDrawable; 16 | import android.os.Bundle; 17 | import android.support.v4.content.ContextCompat; 18 | import android.support.v7.widget.LinearLayoutManager; 19 | import android.support.v7.widget.RecyclerView; 20 | import android.view.ContextThemeWrapper; 21 | import android.view.Gravity; 22 | import android.view.ViewGroup; 23 | import android.widget.ImageView; 24 | import android.widget.TextView; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | import com.sevenheaven.uilibrary.drawables.GroupedAvatarDrawable; 30 | import com.sevenheaven.uilibrary.drawables.MaskDrawable; 31 | import com.sevenheaven.uilibrary.drawables.progressive.ProgressiveDrawable; 32 | import com.sevenheaven.uilibrary.drawables.progressive.providers.AppStoreStyleProgressProvider; 33 | import com.sevenheaven.uilibrary.drawables.progressive.providers.PathProgressProvider; 34 | import com.sevenheaven.uilibrary.shapes.ArcShape; 35 | import com.sevenheaven.uilibrary.shapes.PolygonShape; 36 | import com.sevenheaven.uilibrary.views.AnimatedImageView; 37 | import com.sevenheaven.uilibrarysample.ExampleListAdapter.ExampleItem; 38 | 39 | /** 40 | * Created by 7heaven on 16/8/25. 41 | */ 42 | public class ExampleListActivity extends Activity { 43 | 44 | private RecyclerView mRecyclerView; 45 | private ExampleListAdapter mAdapter; 46 | private List mExampleItemList; 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState){ 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_example_list); 52 | 53 | mExampleItemList = new ArrayList<>(); 54 | mAdapter = new ExampleListAdapter(mExampleItemList); 55 | 56 | mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 57 | mRecyclerView.setAdapter(mAdapter); 58 | mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); 59 | } 60 | 61 | @Override 62 | protected void onResume(){ 63 | super.onResume(); 64 | 65 | mExampleItemList.clear(); 66 | addGroupedAvatarDrawbleToList(); 67 | addAppStoreStyleDrawableToList(); 68 | addPathProgressDrawableToList(); 69 | addMaskDrawableToList(); 70 | addPolygonShapeInteractToList(); 71 | addAnimatedImageViewToList(); 72 | addArcShapeToList(); 73 | } 74 | 75 | private void addArcShapeToList(){ 76 | final ArcShape arcShape = new ArcShape(0, 0, 0); 77 | final ShapeDrawable shapeDrawable = new ShapeDrawable(arcShape); 78 | shapeDrawable.getPaint().setStyle(Paint.Style.STROKE); 79 | shapeDrawable.getPaint().setColor(0xFF0099CC); 80 | shapeDrawable.setPadding(30, 30, 30, 30); 81 | 82 | mExampleItemList.add(new ExampleItem("ArcShape", new ExampleDetailActivity.DetailContentProvider() { 83 | @Override 84 | public Object provideInstance() { 85 | return shapeDrawable; 86 | } 87 | 88 | @Override 89 | public void onGestureMove(float x, float y, int action) { 90 | if(x >= 0 && x <= 1){ 91 | arcShape.setEndAngle(360 * x); 92 | shapeDrawable.invalidateSelf(); 93 | } 94 | 95 | if(y >= 0 && y <= 1){ 96 | arcShape.setStrokeWidth((int) (y * 100)); 97 | shapeDrawable.invalidateSelf(); 98 | } 99 | } 100 | 101 | @Override 102 | public void destroy() { 103 | 104 | } 105 | })); 106 | } 107 | 108 | private void addAnimatedImageViewToList(){ 109 | final AnimatedImageView imageView = new AnimatedImageView(this); 110 | ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 111 | layoutParams.setMargins(50, 50, 50, 50); 112 | imageView.setLayoutParams(layoutParams); 113 | imageView.setImageResource(R.drawable.test2); 114 | 115 | final ImageView.ScaleType[] allScaleTypes = new ImageView.ScaleType[]{ 116 | ImageView.ScaleType.CENTER, 117 | ImageView.ScaleType.CENTER_CROP, 118 | ImageView.ScaleType.CENTER_INSIDE, 119 | ImageView.ScaleType.FIT_CENTER, 120 | ImageView.ScaleType.FIT_END, 121 | ImageView.ScaleType.FIT_START, 122 | ImageView.ScaleType.FIT_XY 123 | }; 124 | 125 | mExampleItemList.add(new ExampleItem("AnimatedImageView", new ExampleDetailActivity.DetailContentProvider() { 126 | @Override 127 | public Object provideInstance() { 128 | return imageView; 129 | } 130 | 131 | @Override 132 | public void onGestureMove(float x, float y, int action) { 133 | if(x >= 0 && x <= 1){ 134 | int index = (int) (x * allScaleTypes.length); 135 | if(index < allScaleTypes.length) imageView.setScaleTypeAnimated(allScaleTypes[index]); 136 | } 137 | } 138 | 139 | @Override 140 | public void destroy(){ 141 | } 142 | })); 143 | } 144 | 145 | /** 146 | * 任意正多边形 147 | */ 148 | private void addPolygonShapeInteractToList(){ 149 | final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 150 | 151 | final int padding = 30; 152 | final PolygonShape shape = new PolygonShape(6, 0.2F, true, 0.5F); 153 | final Drawable contentDrawable = new Drawable() { 154 | @Override 155 | public void draw(Canvas canvas) { 156 | canvas.save(); 157 | canvas.translate(padding, padding); 158 | paint.setStyle(Paint.Style.STROKE); 159 | paint.setStrokeWidth(10); 160 | paint.setColor(0xFF32ADFF); 161 | 162 | shape.draw(canvas, paint); 163 | 164 | canvas.restore(); 165 | } 166 | 167 | @Override 168 | public void setAlpha(int i) { 169 | 170 | } 171 | 172 | @Override 173 | public void setColorFilter(ColorFilter colorFilter) { 174 | 175 | } 176 | 177 | @Override 178 | public int getOpacity() { 179 | return PixelFormat.TRANSPARENT; 180 | } 181 | 182 | @Override 183 | public void onBoundsChange(Rect bounds){ 184 | shape.resize(bounds.width() - padding - padding, bounds.height() - padding - padding); 185 | } 186 | }; 187 | mExampleItemList.add(new ExampleItem("PolygonShapeInteract", new ExampleDetailActivity.DetailContentProvider() { 188 | @Override 189 | public Object provideInstance() { 190 | return contentDrawable; 191 | } 192 | 193 | @Override 194 | public void onGestureMove(float x, float y, int action) { 195 | if(x >= 0 && x <= 1 && y >= 0 && y <= 1){ 196 | shape.setCornerRadius(x / 2); 197 | shape.setVertexCount((int) (y * 10) + 3); 198 | contentDrawable.invalidateSelf(); 199 | } 200 | } 201 | 202 | @Override 203 | public void destroy(){ 204 | 205 | } 206 | })); 207 | } 208 | 209 | /** 210 | * 任意形状的遮罩 211 | */ 212 | private void addMaskDrawableToList(){ 213 | final Drawable contentDrawable = ContextCompat.getDrawable(this, R.drawable.test1); 214 | final PolygonShape shape = new PolygonShape(6, 0.2F); 215 | final MaskDrawable drawable = new MaskDrawable(contentDrawable, shape); 216 | mExampleItemList.add(new ExampleItem("MaskDrawable+PolygonShape", new ExampleDetailActivity.DetailContentProvider() { 217 | @Override 218 | public Object provideInstance() { 219 | return drawable; 220 | } 221 | 222 | @Override 223 | public void onGestureMove(float x, float y, int action) { 224 | if(x >= 0 && x <= 1){ 225 | shape.setVertexCount((int) (x * 6) + 3); 226 | drawable.recreateContent(); 227 | } 228 | } 229 | 230 | @Override 231 | public void destroy(){ 232 | 233 | } 234 | })); 235 | } 236 | 237 | 238 | /** 239 | * 任意Path作为进度条 240 | */ 241 | private void addPathProgressDrawableToList(){ 242 | Paint paint = new Paint(); 243 | paint.setTextSize(100); 244 | 245 | //取得"Android N"字符串的轮廓 246 | Path animatePath = new Path(); 247 | String text = "Android N"; 248 | paint.getTextPath(text, 0, text.length(), 0, paint.getTextSize(), animatePath); 249 | PathProgressProvider.PathDesc animateDesc = new PathProgressProvider.PathDesc(animatePath, Gravity.CENTER, true, false, false); 250 | 251 | RectF animatePathBounds = new RectF(); 252 | animatePath.computeBounds(animatePathBounds, false); 253 | 254 | Path progressPath = new Path(); 255 | progressPath.addCircle(100, 100, animatePathBounds.width() / 2 + 50, Path.Direction.CW); 256 | progressPath.addCircle(100, 100, animatePathBounds.width() / 2 + 80, Path.Direction.CW); 257 | PathProgressProvider.PathDesc pathDesc = new PathProgressProvider.PathDesc(progressPath, Gravity.CENTER, true, true, false); 258 | 259 | ProgressiveDrawable.DrawContentProvider pathProgressProvider = new PathProgressProvider(pathDesc, animateDesc){ 260 | @Override 261 | protected void updateProgressPaint(Paint paint){ 262 | paint.setStyle(Paint.Style.STROKE); 263 | paint.setStrokeWidth(5); 264 | paint.setStrokeCap(Paint.Cap.ROUND); 265 | paint.setColor(0xFF44AEFF); 266 | } 267 | 268 | @Override 269 | protected void updateAnimationPaint(Paint paint){ 270 | paint.setStyle(Paint.Style.STROKE); 271 | paint.setStrokeWidth(2); 272 | paint.setColor(0xFF44AEFF); 273 | } 274 | }; 275 | 276 | final ProgressiveDrawable drawable = new ProgressiveDrawable(pathProgressProvider); 277 | mExampleItemList.add(new ExampleItem("PathProgressDrawable", new ExampleDetailActivity.DetailContentProvider() { 278 | @Override 279 | public Object provideInstance() { 280 | return drawable; 281 | } 282 | 283 | @Override 284 | public void onGestureMove(float x, float y, int action) { 285 | if(x >= -1 && x <= 1){ 286 | drawable.setProgress(x); 287 | } 288 | } 289 | 290 | @Override 291 | public void destroy(){ 292 | 293 | } 294 | })); 295 | } 296 | 297 | /** 298 | * 苹果桌面图标下载风格的进度条 299 | */ 300 | private void addAppStoreStyleDrawableToList(){ 301 | ProgressiveDrawable.DrawContentProvider appStoreProvider = new AppStoreStyleProgressProvider(0x99000000); 302 | 303 | final ProgressiveDrawable drawable = new ProgressiveDrawable(appStoreProvider); 304 | final Drawable backgroundDrawable = ContextCompat.getDrawable(this, R.drawable.test1); 305 | final LayerDrawable contentDrawalbe = new LayerDrawable(new Drawable[]{backgroundDrawable, drawable}); 306 | mExampleItemList.add(new ExampleItem("AppStoreStyleDrawable", new ExampleDetailActivity.DetailContentProvider() { 307 | @Override 308 | public Object provideInstance() { 309 | return contentDrawalbe; 310 | } 311 | 312 | @Override 313 | public void onGestureMove(float x, float y, int action) { 314 | if(x >= -1 && x <= 1){ 315 | drawable.setProgress(x); 316 | } 317 | } 318 | 319 | @Override 320 | public void destroy(){ 321 | 322 | } 323 | })); 324 | } 325 | 326 | /** 327 | * 328 | */ 329 | private void addGroupedAvatarDrawbleToList(){ 330 | final Bitmap[] avatars = new Bitmap[]{BitmapFactory.decodeResource(getResources(), R.drawable.test0), 331 | BitmapFactory.decodeResource(getResources(), R.drawable.test1), 332 | BitmapFactory.decodeResource(getResources(), R.drawable.test2), 333 | BitmapFactory.decodeResource(getResources(), R.drawable.test0), 334 | BitmapFactory.decodeResource(getResources(), R.drawable.test1), 335 | BitmapFactory.decodeResource(getResources(), R.drawable.test2), 336 | BitmapFactory.decodeResource(getResources(), R.drawable.test0), 337 | BitmapFactory.decodeResource(getResources(), R.drawable.test1), 338 | BitmapFactory.decodeResource(getResources(), R.drawable.test2)}; 339 | final GroupedAvatarDrawable avatarDrawable = new GroupedAvatarDrawable(); 340 | avatarDrawable.setStrokeWidth(20); 341 | mExampleItemList.add(new ExampleItem("GroupedAvatarDrawable", new ExampleDetailActivity.DetailContentProvider() { 342 | @Override 343 | public GroupedAvatarDrawable provideInstance() { 344 | return avatarDrawable; 345 | } 346 | 347 | @Override 348 | public void onGestureMove(float x, float y, int action) { 349 | if(x >= 0 && x <= 1){ 350 | int index = (int) (x * avatars.length); 351 | int count = index < avatars.length ? index : avatars.length; 352 | Bitmap[] input = new Bitmap[count]; 353 | for(int i = 0; i < count; i++){ 354 | input[i] = avatars[i]; 355 | } 356 | 357 | avatarDrawable.setAvatars(input); 358 | } 359 | } 360 | 361 | @Override 362 | public void destroy(){ 363 | 364 | } 365 | })); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /example/src/main/java/com/sevenheaven/uilibrarysample/ExampleListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrarysample; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by 7heaven on 16/8/25. 14 | */ 15 | public class ExampleListAdapter extends RecyclerView.Adapter { 16 | 17 | private List mExampleItemList; 18 | 19 | public ExampleListAdapter(List exampleItemList){ 20 | mExampleItemList = exampleItemList; 21 | } 22 | 23 | @Override 24 | public int getItemCount(){ 25 | return mExampleItemList.size(); 26 | } 27 | 28 | @Override 29 | public ExampleListViewHolder onCreateViewHolder(ViewGroup parent, int viewType){ 30 | return new ExampleListViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_examplelist, parent, false)); 31 | } 32 | 33 | @Override 34 | public void onBindViewHolder(ExampleListViewHolder viewHolder, int position){ 35 | final ExampleItem item = mExampleItemList.get(position); 36 | viewHolder.title.setText(item.title); 37 | if(item.provider != null){ 38 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View view) { 41 | Intent intent = new Intent(view.getContext(), ExampleDetailActivity.class); 42 | ExampleDetailActivity.mContentProvider = item.provider; 43 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 44 | view.getContext().startActivity(intent); 45 | } 46 | }); 47 | } 48 | } 49 | 50 | public static class ExampleListViewHolder extends RecyclerView.ViewHolder{ 51 | TextView title; 52 | 53 | public ExampleListViewHolder(View itemView){ 54 | super(itemView); 55 | 56 | title = (TextView) itemView.findViewById(R.id.title); 57 | } 58 | } 59 | 60 | public static class ExampleItem{ 61 | String title; 62 | ExampleDetailActivity.DetailContentProvider provider; 63 | 64 | public ExampleItem(String title, ExampleDetailActivity.DetailContentProvider provider){ 65 | this.title = title; 66 | this.provider = provider; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/src/main/res/drawable-nodpi/test0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/drawable-nodpi/test0.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-nodpi/test1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/drawable-nodpi/test1.png -------------------------------------------------------------------------------- /example/src/main/res/drawable-nodpi/test2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/drawable-nodpi/test2.png -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_example_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 21 | 27 | 32 | 33 | 34 | 40 | 46 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_example_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /example/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /example/src/main/res/layout/item_examplelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /example/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/example/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /example/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /example/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /example/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | UILibrarySample 3 | 4 | -------------------------------------------------------------------------------- /example/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/src/test/java/com/sevenheaven/uilibrarysample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrarysample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7heaven/UILibrary/daa674438a66702320a451aeee18624fa08dc6a1/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.10-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 ':example', ':uilibrary' 2 | -------------------------------------------------------------------------------- /uilibrary/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /uilibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 11 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | testCompile 'junit:junit:4.12' 24 | compile 'com.android.support:appcompat-v7:24.2.0' 25 | } 26 | -------------------------------------------------------------------------------- /uilibrary/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/7heaven/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 | -------------------------------------------------------------------------------- /uilibrary/src/androidTest/java/com/sevenheaven/uilibrary/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /uilibrary/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/GroupedAvatarDrawable.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapShader; 5 | import android.graphics.Canvas; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.PorterDuff; 12 | import android.graphics.PorterDuffXfermode; 13 | import android.graphics.Rect; 14 | import android.graphics.RectF; 15 | import android.graphics.Shader; 16 | import android.graphics.drawable.Drawable; 17 | 18 | import com.sevenheaven.uilibrary.utils.GeomUtil; 19 | 20 | /** 21 | * Drawable that draw a group of images as a circle 22 | * 23 | * Created by 7heaven on 16/8/13. 24 | */ 25 | public class GroupedAvatarDrawable extends Drawable { 26 | 27 | /** 28 | * Cached bitmaps for bounds change recalculation 29 | */ 30 | private Bitmap[] mInputBitmaps; 31 | private Bitmap mSourceBitmap; 32 | 33 | private int mRadiusStrokeWidth; 34 | 35 | private static final float SINGLE_ITEM_OVERLAY_FACTOR = 0.08F; 36 | 37 | private int mCenterX; 38 | private int mCenterY; 39 | 40 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 41 | 42 | public GroupedAvatarDrawable(){ 43 | mRadiusStrokeWidth = 10; 44 | } 45 | 46 | @Override 47 | public int getOpacity(){ 48 | return PixelFormat.TRANSPARENT; 49 | } 50 | 51 | @Override 52 | public void setAlpha(int alpha){ 53 | mPaint.setAlpha(alpha); 54 | } 55 | 56 | @Override 57 | public void setColorFilter(ColorFilter filter){ 58 | mPaint.setColorFilter(filter); 59 | } 60 | 61 | /** 62 | * set avatar bitmaps for drawing 63 | * @param inputBitmaps 64 | */ 65 | public void setAvatars(Bitmap... inputBitmaps){ 66 | mInputBitmaps = inputBitmaps; 67 | 68 | if(getBounds().width() > 0 && getBounds().height() > 0){ 69 | mCenterX = getBounds().width() / 2; 70 | mCenterY = getBounds().height() / 2; 71 | generateSourceBitmap(getBounds()); 72 | 73 | invalidateSelf(); 74 | } 75 | } 76 | 77 | /** 78 | * set stroke width for for single image 79 | * @param strokeWidth 80 | */ 81 | public void setStrokeWidth(int strokeWidth){ 82 | if(mRadiusStrokeWidth != strokeWidth){ 83 | mRadiusStrokeWidth = strokeWidth; 84 | 85 | generateSourceBitmap(getBounds()); 86 | 87 | invalidateSelf(); 88 | } 89 | } 90 | 91 | /** 92 | * Generate source bitmap for drawable drawing 93 | * 94 | * @param bounds 95 | */ 96 | private void generateSourceBitmap(Rect bounds){ 97 | if(mInputBitmaps != null && mInputBitmaps.length > 0){ 98 | final int bitmapCount = mInputBitmaps.length; 99 | 100 | mSourceBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888); 101 | Canvas canvas = new Canvas(mSourceBitmap); 102 | Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 103 | drawPaint.setFilterBitmap(true); 104 | 105 | float fitBoundsRadius = (bounds.width() > bounds.height() ? bounds.height() : bounds.width()) / 2; 106 | if(bitmapCount > 1){ 107 | /** 108 | * To ensure when bitmapCount == 3, each item's radius will equals to fitBoundsRadius / 2 109 | * and radius of single item will decrease when bitmapCount increase 110 | */ 111 | float scaleFactor = bitmapCount < 7 ? (1.0F + SINGLE_ITEM_OVERLAY_FACTOR) - (bitmapCount - 2) * SINGLE_ITEM_OVERLAY_FACTOR : 1.0F * (5.0F / bitmapCount); 112 | float singleRadius = (fitBoundsRadius * scaleFactor) / 2 - mRadiusStrokeWidth; 113 | float outerCircleRadius = fitBoundsRadius - singleRadius - mRadiusStrokeWidth; 114 | 115 | double step = Math.PI * 2.0 / bitmapCount; 116 | 117 | double startAngle; 118 | if((bitmapCount & 1) == 0){ 119 | startAngle = -Math.PI * 0.5 - (bitmapCount == 2 ? Math.PI * 0.25 : step / 2); 120 | }else{ 121 | startAngle = -Math.PI * 0.5F; 122 | } 123 | 124 | float[] centerPoint = new float[2]; 125 | for(int i = 0; i < bitmapCount; i++){ 126 | GeomUtil.pointOnCircumference(mCenterX, mCenterY, startAngle + i * step, outerCircleRadius, centerPoint); 127 | 128 | if(mInputBitmaps[i] != null && !mInputBitmaps[i].isRecycled()) drawBitmapWithStroke(canvas, mInputBitmaps[i], (int) centerPoint[0], (int) centerPoint[1], singleRadius, drawPaint); 129 | } 130 | 131 | if(bitmapCount > 2){ 132 | float outerClipRadius = outerCircleRadius * 2; 133 | RectF outerClipBounds = new RectF(); 134 | outerClipBounds.left = mCenterX - outerClipRadius; 135 | outerClipBounds.top = mCenterY - outerClipRadius; 136 | outerClipBounds.right = mCenterX + outerClipRadius; 137 | outerClipBounds.bottom = mCenterY + outerClipRadius; 138 | 139 | final int savedCount = canvas.save(); 140 | Path clipPath = new Path(); 141 | clipPath.moveTo(mCenterX, mCenterY); 142 | GeomUtil.pointOnCircumference(mCenterX, mCenterY, startAngle + (mInputBitmaps.length - 1) * step, outerClipRadius, centerPoint); 143 | clipPath.arcTo(outerClipBounds, (float) Math.toDegrees((startAngle + (mInputBitmaps.length - 1) * step)), (float) Math.toDegrees(step)); 144 | clipPath.close(); 145 | canvas.clipPath(clipPath); 146 | 147 | GeomUtil.pointOnCircumference(mCenterX, mCenterY, startAngle, outerCircleRadius, centerPoint); 148 | drawBitmapWithStroke(canvas, mInputBitmaps[0], (int) centerPoint[0], (int) centerPoint[1], singleRadius, drawPaint); 149 | 150 | 151 | canvas.restoreToCount(savedCount); 152 | } 153 | }else{ 154 | drawBitmapWithStroke(canvas, mInputBitmaps[0], mCenterX, mCenterY, fitBoundsRadius - mRadiusStrokeWidth, drawPaint); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Draw single bitmap with the giving centerX, centerY and radius 161 | * @param canvas 162 | * @param bitmap 163 | * @param centerX 164 | * @param centerY 165 | * @param radius 166 | * @param drawPaint 167 | */ 168 | private void drawBitmapWithStroke(Canvas canvas, Bitmap bitmap, int centerX, int centerY, float radius, Paint drawPaint){ 169 | drawPaint.setStyle(Paint.Style.FILL); 170 | 171 | float strokeRadius = radius + mRadiusStrokeWidth; 172 | drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 173 | canvas.drawCircle(centerX, centerY, strokeRadius, drawPaint); 174 | drawPaint.setXfermode(null); 175 | 176 | final int halfBitmapWidth = bitmap.getWidth() / 2; 177 | final int halfBitmapHeight = bitmap.getHeight() / 2; 178 | 179 | BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 180 | Matrix shaderMatrix = new Matrix(); 181 | float minSize = bitmap.getWidth() > bitmap.getHeight() ? bitmap.getHeight() : bitmap.getWidth(); 182 | float scale = radius * 2 / minSize; 183 | shaderMatrix.setScale(scale, scale); 184 | shaderMatrix.postTranslate(centerX - (halfBitmapWidth * scale), centerY - (halfBitmapHeight * scale)); 185 | bitmapShader.setLocalMatrix(shaderMatrix); 186 | 187 | drawPaint.setShader(bitmapShader); 188 | canvas.drawCircle(centerX, centerY, radius, drawPaint); 189 | drawPaint.setShader(null); 190 | } 191 | 192 | @Override 193 | protected void onBoundsChange(Rect bounds){ 194 | mCenterX = bounds.width() / 2; 195 | mCenterY = bounds.height() / 2; 196 | 197 | if(bounds.width() > 0 && bounds.height() > 0) generateSourceBitmap(bounds); 198 | } 199 | 200 | @Override 201 | public void draw(Canvas canvas){ 202 | if(mSourceBitmap != null && !mSourceBitmap.isRecycled()){ 203 | canvas.drawBitmap(mSourceBitmap, 0, 0, mPaint); 204 | } 205 | } 206 | 207 | public void recycle(){ 208 | if(mInputBitmaps != null){ 209 | for(int i = 0; i < mInputBitmaps.length; i++){ 210 | Bitmap bitmap = mInputBitmaps[i]; 211 | if(bitmap != null && !bitmap.isRecycled()){ 212 | bitmap.recycle(); 213 | } 214 | } 215 | 216 | mInputBitmaps = null; 217 | } 218 | 219 | if(mSourceBitmap != null && !mSourceBitmap.isRecycled()){ 220 | mSourceBitmap.recycle(); 221 | mSourceBitmap = null; 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/MaskDrawable.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapShader; 5 | import android.graphics.Canvas; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.PixelFormat; 9 | import android.graphics.Rect; 10 | import android.graphics.Shader; 11 | import android.graphics.drawable.Drawable; 12 | import android.graphics.drawable.shapes.Shape; 13 | 14 | /** 15 | * Convenient way to create Drawble with shape-cliped effect 16 | * 17 | * Created by 7heaven on 16/8/25. 18 | */ 19 | public class MaskDrawable extends Drawable { 20 | 21 | private Bitmap mBitmap; 22 | private Canvas mAssociateCanvas; 23 | 24 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 25 | 26 | private Drawable mDrawingContent; 27 | private Shape mMask; 28 | 29 | public MaskDrawable(Drawable content, Shape mask){ 30 | mDrawingContent = content; 31 | mMask = mask; 32 | 33 | mPaint.setStyle(Paint.Style.FILL); 34 | mPaint.setColor(0xFFFFFFFF); 35 | } 36 | 37 | public void setContent(Drawable content){ 38 | mDrawingContent = content; 39 | mDrawingContent.setBounds(getBounds()); 40 | recreateBitmap(); 41 | 42 | invalidateSelf(); 43 | } 44 | 45 | public void setMask(Shape mask){ 46 | mMask = mask; 47 | mMask.resize(getBounds().width(), getBounds().height()); 48 | recreateBitmap(); 49 | 50 | invalidateSelf(); 51 | } 52 | 53 | public void recreateContent(){ 54 | recreateBitmap(); 55 | 56 | invalidateSelf(); 57 | } 58 | 59 | @Override 60 | public void onBoundsChange(Rect bounds){ 61 | if(mDrawingContent != null){ 62 | mDrawingContent.setBounds(0, 0, bounds.width(), bounds.height()); 63 | } 64 | if(mMask != null){ 65 | mMask.resize(bounds.width(), bounds.height()); 66 | } 67 | 68 | recreateBitmap(); 69 | } 70 | 71 | private void recreateBitmap(){ 72 | if(mDrawingContent != null && mMask != null){ 73 | if(mBitmap != null && !mBitmap.isRecycled()){ 74 | mBitmap.recycle(); 75 | mBitmap = null; 76 | } 77 | mBitmap = Bitmap.createBitmap(getBounds().width() == 0 ? 1 : getBounds().width(), getBounds().height() == 0 ? 1 : getBounds().height(), Bitmap.Config.ARGB_8888); 78 | mAssociateCanvas = new Canvas(mBitmap); 79 | 80 | mDrawingContent.draw(mAssociateCanvas); 81 | 82 | mPaint.setShader(new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); 83 | } 84 | } 85 | 86 | @Override 87 | public void draw(Canvas canvas){ 88 | if(mMask != null){ 89 | canvas.save(); 90 | canvas.translate(-getBounds().left, -getBounds().top); 91 | 92 | mMask.draw(canvas, mPaint); 93 | canvas.restore(); 94 | } 95 | } 96 | 97 | @Override 98 | public void setAlpha(int alpha) { 99 | mPaint.setAlpha(alpha); 100 | 101 | invalidateSelf(); 102 | } 103 | 104 | @Override 105 | public void setColorFilter(ColorFilter cf) { 106 | mPaint.setColorFilter(cf); 107 | 108 | invalidateSelf(); 109 | } 110 | 111 | @Override 112 | public int getOpacity() { 113 | return PixelFormat.TRANSLUCENT; 114 | } 115 | 116 | public void recycle(){ 117 | mPaint.setShader(null); 118 | 119 | if(mBitmap != null && !mBitmap.isRecycled()){ 120 | mBitmap.recycle(); 121 | mBitmap = null; 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/RoundEdgeDrawable.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables; 2 | 3 | import android.graphics.Rect; 4 | import android.graphics.drawable.GradientDrawable; 5 | import android.support.annotation.IntDef; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | 10 | /** 11 | * Created by 7heaven on 16/8/8. 12 | */ 13 | public class RoundEdgeDrawable extends GradientDrawable { 14 | 15 | @IntDef({EdgeMark.TOP_LEFT, EdgeMark.TOP_RIGHT, EdgeMark.BOTTOM_RIGHT, EdgeMark.BOTTOM_LEFT}) 16 | @Retention(RetentionPolicy.SOURCE) 17 | public @interface EdgeMark{ 18 | int TOP_LEFT = 0x1; 19 | int TOP_RIGHT = 0x2; 20 | int BOTTOM_RIGHT = 0x4; 21 | int BOTTOM_LEFT = 0x8; 22 | } 23 | 24 | private int mEdgeMark; 25 | 26 | public RoundEdgeDrawable(){ 27 | super(); 28 | mEdgeMark = 0x0; 29 | } 30 | 31 | public RoundEdgeDrawable(GradientDrawable.Orientation orientation, int[] colors){ 32 | super(orientation, colors); 33 | mEdgeMark = 0x0; 34 | } 35 | 36 | public RoundEdgeDrawable(GradientDrawable.Orientation orientation, int[] colors, @EdgeMark int edgeMark){ 37 | super(orientation, colors); 38 | mEdgeMark = edgeMark; 39 | } 40 | 41 | public void setEdgeMark(@EdgeMark int edgeMark){ 42 | if(mEdgeMark != edgeMark){ 43 | mEdgeMark = edgeMark; 44 | recalculateRadius(getBounds()); 45 | } 46 | } 47 | 48 | @Override 49 | public void setCornerRadius(float radius){ 50 | 51 | } 52 | 53 | @Override 54 | public void setCornerRadii(float[] radii){ 55 | 56 | } 57 | 58 | private void recalculateRadius(Rect bounds){ 59 | int verticalHalf = bounds.height() / 2; 60 | int horizontalHalf = bounds.width() / 2; 61 | 62 | float calculatedRadius = verticalHalf > horizontalHalf ? horizontalHalf : verticalHalf; 63 | if(mEdgeMark == 0x0 || (mEdgeMark & (EdgeMark.TOP_LEFT | EdgeMark.TOP_RIGHT | EdgeMark.BOTTOM_LEFT | EdgeMark.BOTTOM_RIGHT)) > 0){ 64 | super.setCornerRadius(calculatedRadius); 65 | }else{ 66 | float topLeftRadius = 0; 67 | float topRightRadius = 0; 68 | float bottomRightRadius = 0; 69 | float bottomLeftRadius = 0; 70 | 71 | if((mEdgeMark & EdgeMark.TOP_LEFT) > 0){ 72 | topLeftRadius = calculatedRadius; 73 | } 74 | 75 | if((mEdgeMark & EdgeMark.TOP_RIGHT) > 0){ 76 | topRightRadius = calculatedRadius; 77 | } 78 | 79 | if((mEdgeMark & EdgeMark.BOTTOM_LEFT) > 0){ 80 | bottomLeftRadius = calculatedRadius; 81 | } 82 | 83 | if((mEdgeMark & EdgeMark.BOTTOM_RIGHT) > 0){ 84 | bottomRightRadius = calculatedRadius; 85 | } 86 | 87 | super.setCornerRadii(new float[]{topLeftRadius, 88 | topLeftRadius, 89 | topRightRadius, 90 | topRightRadius, 91 | bottomRightRadius, 92 | bottomRightRadius, 93 | bottomLeftRadius, 94 | bottomLeftRadius}); 95 | } 96 | } 97 | 98 | @Override 99 | protected void onBoundsChange(Rect bounds){ 100 | 101 | recalculateRadius(bounds); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/progressive/LoadingDrawable.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables.progressive; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | 6 | /** 7 | * Created by 7heaven on 16/8/8. 8 | */ 9 | public abstract class LoadingDrawable extends ProgressiveDrawable { 10 | 11 | private Animator.AnimatorListener startAnimationListener = new Animator.AnimatorListener(){ 12 | @Override 13 | public void onAnimationStart(Animator animator) { 14 | 15 | } 16 | 17 | @Override 18 | public void onAnimationEnd(Animator animator) { 19 | performProgressiveAnimation(); 20 | 21 | mAnimator.removeListener(this); 22 | } 23 | 24 | @Override 25 | public void onAnimationCancel(Animator animator) { 26 | 27 | } 28 | 29 | @Override 30 | public void onAnimationRepeat(Animator animator) { 31 | 32 | } 33 | }; 34 | 35 | private Animator.AnimatorListener progressiveAnimationListener = new Animator.AnimatorListener(){ 36 | @Override 37 | public void onAnimationStart(Animator animator) { 38 | 39 | } 40 | 41 | @Override 42 | public void onAnimationEnd(Animator animator) { 43 | } 44 | 45 | @Override 46 | public void onAnimationCancel(Animator animator) { 47 | } 48 | 49 | @Override 50 | public void onAnimationRepeat(Animator animator) { 51 | 52 | } 53 | }; 54 | 55 | private ValueAnimator mProgressiveAnimator; 56 | 57 | public LoadingDrawable(DrawContentProvider contentProvider){ 58 | super(contentProvider); 59 | } 60 | 61 | public void setInvertLoadingAnimation(boolean invert){ 62 | 63 | } 64 | 65 | public void startLoading(){ 66 | mAnimator.addListener(startAnimationListener); 67 | performStartAnimation(); 68 | } 69 | 70 | public void stopLoading(){ 71 | mProgressiveAnimator.end(); 72 | 73 | performEndAnimation(); 74 | } 75 | 76 | // TODO implement progressive animation to make a circling animation for progress content drawing 77 | private void performProgressiveAnimation(){ 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/progressive/ProgressiveDrawable.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables.progressive; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.graphics.Canvas; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.PixelFormat; 8 | import android.graphics.Rect; 9 | import android.graphics.drawable.Drawable; 10 | import android.support.annotation.FloatRange; 11 | import android.support.annotation.IntDef; 12 | import android.support.annotation.NonNull; 13 | import android.view.animation.DecelerateInterpolator; 14 | import android.view.animation.Interpolator; 15 | 16 | import java.lang.annotation.Retention; 17 | import java.lang.annotation.RetentionPolicy; 18 | 19 | /** 20 | * Drawable for progress content drawing 21 | * 22 | * Created by 7heaven on 16/8/5. 23 | */ 24 | public class ProgressiveDrawable extends Drawable{ 25 | 26 | public static float PROGRESS_IDLE = -1; 27 | 28 | private float mProgress = 0; 29 | private float mAnimatedValue = 0; 30 | 31 | ValueAnimator mAnimator; 32 | 33 | private long mShowDuration = -1; 34 | private long mDismissDuration = -1; 35 | private static final long DEFAULT_ANIMATION_DURATION = 400L; 36 | 37 | private Interpolator mShowInterpolator; 38 | private Interpolator mDismissInterpolator; 39 | private static final Interpolator DEFAULT_ANIMATION_INTERPOLATOR = new DecelerateInterpolator(); 40 | 41 | @AnimationType int mCurrentAnimationType; 42 | 43 | private DrawContentProvider mDrawContentProvider; 44 | 45 | /** 46 | * Class for provide progressive and animation content change 47 | */ 48 | public static abstract class DrawContentProvider{ 49 | 50 | private ProgressiveDrawable mHost; 51 | 52 | /** 53 | * Call when the host's bound changed 54 | * @param bounds 55 | */ 56 | protected abstract void onBoundsChange(Rect bounds); 57 | 58 | /** 59 | * Draw progressive content and animatable content if need. 60 | * @param canvas the canvas which the content should be draw on. 61 | * @param progress current progress 62 | */ 63 | protected abstract void drawProgress(Canvas canvas, @FloatRange(from=0.0F, to=1.0F) float progress); 64 | 65 | /** 66 | * Update the value for animation 67 | * 68 | * @param type AnimationType 69 | * @param value animated value 70 | */ 71 | protected abstract void animationUpdate(@AnimationType int type, @FloatRange(from=0.0F, to=1.0F) float value); 72 | 73 | final protected void invalidateContent(){ 74 | if(mHost != null){ 75 | mHost.invalidateSelf(); 76 | } 77 | } 78 | 79 | void setHost(ProgressiveDrawable hostDrawable){ 80 | if(hostDrawable == null){ 81 | mHost = null; 82 | }else{ 83 | mHost = hostDrawable; 84 | } 85 | } 86 | } 87 | 88 | @IntDef({AnimationType.SHOW_ANIMATION, AnimationType.DISMISS_ANIMATION, AnimationType.IDLE}) 89 | @Retention(RetentionPolicy.SOURCE) 90 | public @interface AnimationType{ 91 | int SHOW_ANIMATION = 0; 92 | int DISMISS_ANIMATION = 1; 93 | int IDLE = 2; 94 | } 95 | 96 | public ProgressiveDrawable(@NonNull DrawContentProvider contentProvider){ 97 | if(contentProvider == null) throw new IllegalArgumentException("content provider must not be null!"); 98 | 99 | mDrawContentProvider = contentProvider; 100 | mDrawContentProvider.setHost(this); 101 | 102 | mAnimator = ValueAnimator.ofFloat(0, 1); 103 | mAnimator.setDuration(DEFAULT_ANIMATION_DURATION); 104 | mAnimator.setInterpolator(DEFAULT_ANIMATION_INTERPOLATOR); 105 | mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 106 | @Override 107 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 108 | mAnimatedValue = (float) valueAnimator.getAnimatedValue(); 109 | 110 | mDrawContentProvider.animationUpdate(mCurrentAnimationType, mAnimatedValue); 111 | } 112 | }); 113 | mAnimator.addListener(new Animator.AnimatorListener() { 114 | @Override 115 | public void onAnimationStart(Animator animator) { 116 | 117 | } 118 | 119 | @Override 120 | public void onAnimationEnd(Animator animator) { 121 | mCurrentAnimationType = AnimationType.IDLE; 122 | if(mProgress > 0 && mProgress < 1){ 123 | mAnimatedValue = 1; 124 | mDrawContentProvider.animationUpdate(mCurrentAnimationType, mAnimatedValue); 125 | } 126 | } 127 | 128 | @Override 129 | public void onAnimationCancel(Animator animator) { 130 | 131 | } 132 | 133 | @Override 134 | public void onAnimationRepeat(Animator animator) { 135 | 136 | } 137 | }); 138 | } 139 | 140 | public void setDrawContentProvider(DrawContentProvider provider){ 141 | mDrawContentProvider = provider; 142 | 143 | if(mDrawContentProvider != null){ 144 | mDrawContentProvider.onBoundsChange(getBounds()); 145 | invalidateSelf(); 146 | } 147 | } 148 | 149 | public DrawContentProvider getDrawContentProvider(){ 150 | return mDrawContentProvider; 151 | } 152 | 153 | /** 154 | * Set the interpolator for progress bar init animation 155 | * @param interpolator 156 | */ 157 | public void setShowAnimationInterpolator(Interpolator interpolator){ 158 | mShowInterpolator = interpolator; 159 | } 160 | 161 | /** 162 | * Set the interpolator for progress bar dismiss animation 163 | * @param interpolator 164 | */ 165 | public void setDismissAnimationInterpolator(Interpolator interpolator){ 166 | mDismissInterpolator = interpolator; 167 | } 168 | 169 | /** 170 | * Set the duration for progress bar init animation 171 | * @param duration 172 | */ 173 | public void setShowAnimationDuration(long duration){ 174 | mShowDuration = duration; 175 | } 176 | 177 | /** 178 | * Set the duration for progress bar dismiss duration 179 | * @param duration 180 | */ 181 | public void setDismissAnimationDuration(long duration){ 182 | mDismissDuration = duration; 183 | } 184 | 185 | /** 186 | * Set the progress 187 | * @param progress 188 | */ 189 | public void setProgress(float progress){ 190 | if(progress == 0){ 191 | performStartAnimation(); 192 | }else if(progress == PROGRESS_IDLE){ 193 | performEndAnimation(); 194 | }else if(mAnimatedValue != 1 && !mAnimator.isRunning()){ 195 | mAnimatedValue = 1; 196 | mDrawContentProvider.animationUpdate(AnimationType.IDLE, mAnimatedValue); 197 | } 198 | 199 | mProgress = progress; 200 | 201 | invalidateSelf(); 202 | } 203 | 204 | void performStartAnimation(){ 205 | if(mAnimator.isRunning()){ 206 | mAnimator.end(); 207 | mAnimator.cancel(); 208 | } 209 | 210 | if(mShowInterpolator != null) mAnimator.setInterpolator(mShowInterpolator); 211 | if(mShowDuration != -1) mAnimator.setDuration(mShowDuration); 212 | 213 | if(mCurrentAnimationType == AnimationType.DISMISS_ANIMATION) mAnimator.setFloatValues(1 - mAnimatedValue, 1); 214 | mCurrentAnimationType = AnimationType.SHOW_ANIMATION; 215 | 216 | mAnimator.start(); 217 | } 218 | 219 | void performEndAnimation(){ 220 | if(mAnimator.isRunning()){ 221 | mAnimator.end(); 222 | mAnimator.cancel(); 223 | } 224 | if(mDismissInterpolator != null) mAnimator.setInterpolator(mDismissInterpolator); 225 | if(mDismissDuration != -1) mAnimator.setDuration(mDismissDuration); 226 | 227 | if(mCurrentAnimationType == AnimationType.SHOW_ANIMATION) mAnimator.setFloatValues(1 - mAnimatedValue, 1); 228 | mCurrentAnimationType = AnimationType.DISMISS_ANIMATION; 229 | 230 | mAnimator.setFloatValues(1 - mAnimatedValue, 1); 231 | 232 | mAnimator.start(); 233 | } 234 | 235 | @Override 236 | public void setAlpha(int alpha){ 237 | 238 | } 239 | 240 | @Override 241 | public void setColorFilter(ColorFilter filter){ 242 | 243 | } 244 | 245 | @Override 246 | public int getOpacity(){ 247 | return PixelFormat.TRANSPARENT; 248 | } 249 | 250 | @Override 251 | protected void onBoundsChange(Rect bounds){ 252 | mDrawContentProvider.onBoundsChange(bounds); 253 | } 254 | 255 | @Override 256 | public void draw(Canvas canvas){ 257 | mDrawContentProvider.drawProgress(canvas, mProgress); 258 | } 259 | 260 | } 261 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/progressive/providers/AppStoreStyleProgressProvider.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables.progressive.providers; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.Rect; 7 | import android.graphics.RectF; 8 | import android.support.annotation.FloatRange; 9 | 10 | import com.sevenheaven.uilibrary.drawables.progressive.ProgressiveDrawable; 11 | 12 | /** 13 | * Created by 7heaven on 16/8/8. 14 | */ 15 | public class AppStoreStyleProgressProvider extends ProgressiveDrawable.DrawContentProvider { 16 | 17 | private Path path = new Path(); 18 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 19 | private RectF mOval = new RectF(); 20 | private RectF mOuterBound = new RectF(); 21 | 22 | private int mRadius; 23 | private int mProgressRadiusToFit; 24 | private int mFitRadius; 25 | 26 | private int mCenterX; 27 | private int mCenterY; 28 | 29 | private int mColor; 30 | private int mAlpha; 31 | 32 | public AppStoreStyleProgressProvider(int color){ 33 | mColor = color; 34 | mAlpha = (color & 0xFF000000) >>> 24; 35 | mPaint.setColor(mColor); 36 | } 37 | 38 | @Override 39 | protected void onBoundsChange(Rect bounds){ 40 | mCenterX = bounds.width() / 2; 41 | mCenterY = bounds.height() / 2; 42 | 43 | int size = mCenterX > mCenterY ? mCenterY : mCenterX; 44 | 45 | mFitRadius = (int) (size * 0.8F); 46 | 47 | //calculate the distance between outer radius and inner progress circle radius 48 | mProgressRadiusToFit = (int) (mFitRadius * 0.15F); 49 | 50 | mOuterBound.left = 0; 51 | mOuterBound.top = 0; 52 | mOuterBound.right = bounds.width(); 53 | mOuterBound.bottom = bounds.height(); 54 | } 55 | 56 | private void updateRadius(int radius){ 57 | mRadius = radius; 58 | 59 | int progressRadius = mRadius < mProgressRadiusToFit ? 0 : mRadius - mProgressRadiusToFit; 60 | 61 | mOval.left = mCenterX - progressRadius; 62 | mOval.right = mCenterX + progressRadius; 63 | mOval.top = mCenterY - progressRadius; 64 | mOval.bottom = mCenterY + progressRadius; 65 | 66 | invalidateContent(); 67 | } 68 | 69 | @Override 70 | protected void animationUpdate(@ProgressiveDrawable.AnimationType int type, @FloatRange(from=0.0F, to=1.0F) float value){ 71 | switch(type){ 72 | case ProgressiveDrawable.AnimationType.SHOW_ANIMATION: 73 | case ProgressiveDrawable.AnimationType.IDLE: 74 | int realAlpha = (int) (value * mAlpha); 75 | mPaint.setColor((realAlpha << 24) | (mColor & 0xFFFFFF)); 76 | 77 | updateRadius((int) (value * mFitRadius)); 78 | break; 79 | case ProgressiveDrawable.AnimationType.DISMISS_ANIMATION: 80 | updateRadius((int) (((value * 1.5) + 1) * mFitRadius)); 81 | break; 82 | } 83 | } 84 | 85 | @Override 86 | protected void drawProgress(Canvas canvas, @FloatRange(from=0.0F, to=1.0F) float progress){ 87 | path.reset(); 88 | 89 | //add outer rect 90 | path.addRect(mOuterBound, Path.Direction.CCW); 91 | 92 | //punch a hole 93 | path.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW); 94 | 95 | canvas.drawPath(path, mPaint); 96 | 97 | if(progress >= 0){ 98 | float angle = (1.0F - progress) * 360; 99 | float startAngle = -90 + (360 - angle); 100 | canvas.drawArc(mOval, startAngle, angle, true, mPaint); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/drawables/progressive/providers/PathProgressProvider.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.drawables.progressive.providers; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Matrix; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.Rect; 8 | import android.graphics.RectF; 9 | import android.view.Gravity; 10 | 11 | import com.sevenheaven.uilibrary.drawables.progressive.ProgressiveDrawable; 12 | import com.sevenheaven.uilibrary.utils.GeomUtil; 13 | import com.sevenheaven.uilibrary.utils.PathMeasurement; 14 | 15 | /** 16 | * Draw content progressively with the provided path 17 | * 18 | * Created by 7heaven on 16/8/8. 19 | */ 20 | public class PathProgressProvider extends ProgressiveDrawable.DrawContentProvider { 21 | 22 | private Path[] mDrawingProgressPaths; 23 | private Path[] mDrawingAnimationPaths; 24 | private Path mDrawingProgressConcatPath; 25 | private Path mDrawingAnimationConcatPath; 26 | 27 | private PathMeasurement mProgressPathMeasurement; 28 | private PathMeasurement mAnimationPathMeasurement; 29 | 30 | private Paint mPaint; 31 | 32 | /** 33 | * Path description 34 | */ 35 | public static class PathDesc{ 36 | Path mPath; 37 | 38 | final int mGravity; 39 | 40 | RectF mBounds; 41 | Rect mBoundsInt; 42 | 43 | final boolean mSubPathsProgressiveAsync; 44 | final boolean mKeepAspect; 45 | final boolean mScaleForBounds; 46 | 47 | private PathMeasurement mAssociatePathMeasurement; 48 | 49 | public PathDesc(Path path, int gravity, boolean subPathsProgressiveAsync){ 50 | this(path, gravity, subPathsProgressiveAsync, true, true); 51 | } 52 | 53 | /** 54 | * Constructor for create a new PathDesc 55 | * 56 | * @param path main path 57 | * @param gravity gravity for path in the ProgressiveDrawble 58 | * @param subPathsProgressiveAsync indicate if path should draw progressively based on each contour's length or the entire length 59 | * @param keepAspect indicate if path should keep aspect when scaleForBounds == true 60 | * @param scaleForBounds indicate if path should scale when drawable bounds change 61 | */ 62 | public PathDesc(Path path, int gravity, boolean subPathsProgressiveAsync, boolean keepAspect, boolean scaleForBounds){ 63 | mPath = path; 64 | mBounds = new RectF(); 65 | mBoundsInt = new Rect(); 66 | 67 | mGravity = gravity; 68 | mKeepAspect = keepAspect; 69 | mScaleForBounds = scaleForBounds; 70 | mSubPathsProgressiveAsync = subPathsProgressiveAsync; 71 | 72 | computeBounds(); 73 | } 74 | 75 | void computeBounds(){ 76 | mPath.computeBounds(mBounds, false); 77 | mBoundsInt.left = (int) mBounds.left; 78 | mBoundsInt.top = (int) mBounds.top; 79 | mBoundsInt.right = (int) mBounds.right; 80 | mBoundsInt.bottom = (int) mBounds.bottom; 81 | } 82 | } 83 | 84 | private PathDesc mProgressPathDesc; 85 | private PathDesc mAnimationPathDesc; 86 | 87 | private Rect pathDescOutRect; 88 | 89 | public PathProgressProvider(PathDesc progressPathDesc, PathDesc animationPathDesc){ 90 | mProgressPathDesc = progressPathDesc; 91 | mAnimationPathDesc = animationPathDesc; 92 | 93 | if(mProgressPathDesc != null){ 94 | mProgressPathMeasurement = new PathMeasurement(mProgressPathDesc.mPath); 95 | mProgressPathDesc.mAssociatePathMeasurement = mProgressPathMeasurement; 96 | } 97 | if(mAnimationPathDesc != null){ 98 | mAnimationPathMeasurement = new PathMeasurement(mAnimationPathDesc.mPath); 99 | mAnimationPathDesc.mAssociatePathMeasurement = mAnimationPathMeasurement; 100 | } 101 | 102 | mDrawingProgressConcatPath = new Path(); 103 | mDrawingAnimationConcatPath = new Path(); 104 | 105 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 106 | } 107 | 108 | private void transformPath(PathDesc pathDesc, Rect targetBounds){ 109 | pathDesc.computeBounds(); 110 | 111 | float scaleX = 1; 112 | float scaleY = 1; 113 | 114 | if(pathDesc.mScaleForBounds){ 115 | if(pathDesc.mKeepAspect){ 116 | float oRatio = pathDesc.mBounds.width() / pathDesc.mBounds.height(); 117 | float tRatio = (float) targetBounds.width() / (float) targetBounds.height(); 118 | 119 | if(oRatio > tRatio){ 120 | float scale = (float) targetBounds.width() / pathDesc.mBounds.width(); 121 | 122 | scaleX = scale; 123 | scaleY = scale; 124 | }else{ 125 | float scale = (float) targetBounds.height() / pathDesc.mBounds.height(); 126 | 127 | scaleX = scale; 128 | scaleY = scale; 129 | } 130 | }else{ 131 | scaleX = (float) targetBounds.width() / pathDesc.mBounds.width(); 132 | scaleY = (float) targetBounds.height() / pathDesc.mBounds.height(); 133 | } 134 | } 135 | 136 | int w = (int) (pathDesc.mBounds.width() * scaleX); 137 | int h = (int) (pathDesc.mBounds.height() * scaleY); 138 | 139 | if(pathDescOutRect == null) pathDescOutRect = new Rect(); 140 | 141 | Gravity.apply(pathDesc.mGravity, w, h, targetBounds, pathDescOutRect); 142 | 143 | Matrix transformMatrix = new Matrix(); 144 | GeomUtil.getTransformationMatrix(pathDesc.mBoundsInt, pathDescOutRect, transformMatrix); 145 | pathDesc.mPath.transform(transformMatrix); 146 | 147 | pathDesc.computeBounds(); 148 | 149 | if(pathDesc.mAssociatePathMeasurement != null){ 150 | pathDesc.mAssociatePathMeasurement.setPath(pathDesc.mPath); 151 | } 152 | } 153 | 154 | @Override 155 | protected void onBoundsChange(Rect bounds){ 156 | if(mAnimationPathDesc != null) transformPath(mAnimationPathDesc, bounds); 157 | if(mProgressPathDesc != null) transformPath(mProgressPathDesc, bounds); 158 | } 159 | 160 | /** 161 | * call when path's position changed 162 | * @param distance 163 | * @param pos 164 | * @param tan 165 | */ 166 | protected void onPathPositionUpdate(Path invokedPath, float distance, float[] pos, float[] tan){} 167 | 168 | protected void updateProgressPaint(Paint paint){ 169 | paint.setColor(0xFF0099CC); 170 | } 171 | protected void updateAnimationPaint(Paint paint){ 172 | paint.setColor(0xFFCC9900); 173 | } 174 | 175 | @Override 176 | protected void animationUpdate(@ProgressiveDrawable.AnimationType int type, float value){ 177 | if(mAnimationPathMeasurement != null){ 178 | switch(type){ 179 | case ProgressiveDrawable.AnimationType.IDLE: 180 | case ProgressiveDrawable.AnimationType.SHOW_ANIMATION: 181 | mDrawingAnimationPaths = mAnimationPathMeasurement.updatePhare(value, mAnimationPathDesc.mSubPathsProgressiveAsync); 182 | break; 183 | case ProgressiveDrawable.AnimationType.DISMISS_ANIMATION: 184 | mDrawingAnimationPaths = mAnimationPathMeasurement.updatePhare(1 - value, mAnimationPathDesc.mSubPathsProgressiveAsync); 185 | break; 186 | } 187 | 188 | invalidateContent(); 189 | } 190 | } 191 | 192 | @Override 193 | protected void drawProgress(Canvas canvas, float progress){ 194 | //draw the animation start/end path 195 | if(mDrawingAnimationPaths != null){ 196 | updateAnimationPaint(mPaint); 197 | mDrawingAnimationConcatPath.reset(); 198 | 199 | for(int i = 0; i < mDrawingAnimationPaths.length; i++){ 200 | Path path = mDrawingAnimationPaths[i]; 201 | if(path != null) mDrawingAnimationConcatPath.addPath(mDrawingAnimationPaths[i]); 202 | } 203 | 204 | canvas.drawPath(mDrawingAnimationConcatPath, mPaint); 205 | } 206 | 207 | //draw the progressive path 208 | if(mProgressPathMeasurement != null){ 209 | 210 | mDrawingProgressPaths = mProgressPathMeasurement.updatePhare(progress, mProgressPathDesc.mSubPathsProgressiveAsync); 211 | 212 | if(mDrawingProgressPaths != null){ 213 | updateProgressPaint(mPaint); 214 | mDrawingProgressConcatPath.reset(); 215 | 216 | for(int i = 0; i < mDrawingProgressPaths.length; i++){ 217 | Path path = mDrawingProgressPaths[i]; 218 | if(path != null) mDrawingProgressConcatPath.addPath(path); 219 | } 220 | 221 | canvas.drawPath(mDrawingProgressConcatPath, mPaint); 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/shapes/ArcShape.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.shapes; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.RectF; 7 | import android.graphics.drawable.shapes.Shape; 8 | 9 | import com.sevenheaven.uilibrary.utils.GeomUtil; 10 | 11 | /** 12 | * Created by 7heaven on 2017/2/3. 13 | */ 14 | 15 | public class ArcShape extends Shape { 16 | 17 | private float mStartAngle; 18 | private float mEndAngle; 19 | 20 | private int mStrokeWidth; 21 | 22 | private Path mComputedPath; 23 | 24 | public ArcShape(float startAngle, float endAngle, int strokeWidth){ 25 | mStartAngle = startAngle; 26 | mEndAngle = endAngle; 27 | 28 | mStrokeWidth = strokeWidth; 29 | 30 | recomputeShape(); 31 | } 32 | 33 | public void setStartAngle(float startAngle){ 34 | if(startAngle != mStartAngle){ 35 | mStartAngle = startAngle; 36 | 37 | recomputeShape(); 38 | } 39 | } 40 | 41 | public void setEndAngle(float endAngle){ 42 | if(endAngle != mEndAngle){ 43 | mEndAngle = endAngle; 44 | 45 | recomputeShape(); 46 | } 47 | } 48 | 49 | public void setStrokeWidth(int strokeWidth){ 50 | if(strokeWidth != mStrokeWidth){ 51 | mStrokeWidth = strokeWidth; 52 | 53 | recomputeShape(); 54 | } 55 | } 56 | 57 | private void recomputeShape(){ 58 | float centerX = getWidth() / 2; 59 | float centerY = getHeight() / 2; 60 | 61 | int halfStrokeWidth = mStrokeWidth / 2; 62 | 63 | float radius = (centerX > centerY ? centerY : centerX) - halfStrokeWidth; 64 | 65 | float innerRadius = radius - halfStrokeWidth; 66 | float outerRadius = radius + halfStrokeWidth; 67 | 68 | if(mComputedPath == null) mComputedPath = new Path(); 69 | mComputedPath.reset(); 70 | 71 | RectF oval = new RectF(); 72 | ovalSet(centerX, centerY, outerRadius, oval); 73 | 74 | mComputedPath.arcTo(oval, mStartAngle, mEndAngle - mStartAngle, true); 75 | 76 | float[] endPoint = GeomUtil.pointOnCircumference((int) centerX, (int) centerY, Math.toRadians(mEndAngle), radius); 77 | ovalSet(endPoint[0], endPoint[1], halfStrokeWidth, oval); 78 | 79 | mComputedPath.arcTo(oval, mEndAngle, 180, false); 80 | 81 | ovalSet(centerX, centerY, innerRadius, oval); 82 | 83 | mComputedPath.arcTo(oval, mEndAngle, mStartAngle - mEndAngle, false); 84 | 85 | float[] startPoint = GeomUtil.pointOnCircumference((int) centerX, (int) centerY, Math.toRadians(mStartAngle), radius); 86 | ovalSet(startPoint[0], startPoint[1], halfStrokeWidth, oval); 87 | 88 | mComputedPath.arcTo(oval, mStartAngle - 180, 180, false); 89 | 90 | mComputedPath.close(); 91 | } 92 | 93 | private void ovalSet(float centerX, float centerY, float radius, RectF outOval){ 94 | outOval.left = centerX - radius; 95 | outOval.top = centerY - radius; 96 | outOval.right = centerX + radius; 97 | outOval.bottom = centerY + radius; 98 | } 99 | 100 | @Override 101 | protected void onResize(float width, float height){ 102 | 103 | recomputeShape(); 104 | } 105 | 106 | @Override 107 | public void draw(Canvas canvas, Paint paint){ 108 | if(mComputedPath != null) canvas.drawPath(mComputedPath, paint); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/shapes/PolygonShape.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.shapes; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.PointF; 7 | import android.graphics.RectF; 8 | import android.graphics.drawable.shapes.Shape; 9 | import android.support.annotation.FloatRange; 10 | import android.support.annotation.IntRange; 11 | 12 | import com.sevenheaven.uilibrary.utils.GeomUtil; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Shape for create a regular polygon or star with round corner 19 | * 20 | * Created by 7heaven on 16/4/25. 21 | */ 22 | public class PolygonShape extends Shape { 23 | 24 | private int mVertexCount; 25 | private float mCornerRadius; 26 | private boolean mAsStar; 27 | 28 | private List mControlPoints; 29 | 30 | private float mStarRatio = 0.65F; 31 | private static final double startAngle = -(Math.PI / 2); 32 | 33 | private Path starPath; 34 | 35 | public PolygonShape(int vertexCount, float cornerRadius){ 36 | this(vertexCount, cornerRadius, false, -1); 37 | } 38 | 39 | public PolygonShape(int vertexCount, float cornerRadius, boolean star){ 40 | this(vertexCount, cornerRadius, star, -1); 41 | } 42 | 43 | public PolygonShape(@IntRange(from=0, to=Integer.MAX_VALUE) int vertexCount, @FloatRange(from=0, to=0.5) float cornerRadius, boolean star, float starRatio){ 44 | if(cornerRadius > 0.5F || cornerRadius < 0) throw new IllegalArgumentException("corners range is (0 - 0.5)"); 45 | this.mVertexCount = vertexCount; 46 | this.mCornerRadius = cornerRadius; 47 | this.mAsStar = star; 48 | this.mStarRatio = starRatio == -1 ? this.mStarRatio : starRatio; 49 | 50 | starPath = new Path(); 51 | 52 | mControlPoints = new ArrayList<>(); 53 | 54 | } 55 | 56 | public void setVertexCount(int vertexCount){ 57 | if(this.mVertexCount != vertexCount){ 58 | this.mVertexCount = vertexCount; 59 | 60 | calculateOutline(); 61 | } 62 | } 63 | 64 | public void setCornerRadius(@FloatRange(from=0, to=0.5) float cornerRadius){ 65 | if(this.mCornerRadius != cornerRadius){ 66 | this.mCornerRadius = cornerRadius; 67 | 68 | calculateOutline(); 69 | } 70 | } 71 | 72 | public void setAsStar(boolean asStar){ 73 | if(this.mAsStar != asStar){ 74 | this.mAsStar = asStar; 75 | 76 | calculateOutline(); 77 | } 78 | } 79 | 80 | @Override 81 | public void onResize(float width, float height){ 82 | calculateOutline(); 83 | } 84 | 85 | 86 | @Override 87 | public void draw(Canvas canvas, Paint paint){ 88 | 89 | if(canvas != null) canvas.drawPath(starPath, paint); 90 | } 91 | 92 | private void calculateOutline(){ 93 | starPath.reset(); 94 | mControlPoints.clear(); 95 | 96 | RectF bound = new RectF(0, 0, getWidth(), getHeight()); 97 | int radius = (int) ((bound.width() > bound.height() ? bound.height() : bound.width()) * 0.5F); 98 | int centerX = (int) (bound.width() * 0.5F); 99 | int centerY = (int) (bound.height() * 0.5F); 100 | 101 | double angle = startAngle; 102 | double angleStep = Math.PI * 2 / mVertexCount; 103 | double halfStep = angleStep * 0.5F; 104 | float[] positions; 105 | 106 | //check if corner radius is zero, to prevent unnecessary calculation 107 | if(mCornerRadius == 0){ 108 | positions = GeomUtil.pointOnCircumference(centerX, centerY, angle, radius); 109 | starPath.moveTo(positions[0], positions[1]); 110 | if(mAsStar){ 111 | positions = GeomUtil.pointOnCircumference(centerX, centerY, angle + halfStep, radius * mStarRatio); 112 | starPath.lineTo(positions[0], positions[1]); 113 | } 114 | for(int i = 0; i < mVertexCount - 1; i++){ 115 | angle += angleStep; 116 | positions = GeomUtil.pointOnCircumference(centerX, centerY, angle, radius); 117 | starPath.lineTo(positions[0], positions[1]); 118 | 119 | if(mAsStar){ 120 | positions = GeomUtil.pointOnCircumference(centerX, centerY, angle + halfStep, radius * mStarRatio); 121 | starPath.lineTo(positions[0], positions[1]); 122 | } 123 | } 124 | 125 | starPath.close(); 126 | }else{ 127 | 128 | /** 129 | * 130 | * /\ \ 131 | * \/ <----- ) When drawing a round corner Polygon, each corner contains a curve and a straight line, 132 | * / Curve represent the round corner and connect to the next corner using the straight line, 133 | * the Curve is a Bezier curve, the control point of the bezier curve is the vertex of the 134 | * corner, and the start point and the end point are on the straight lines representing this 135 | * corner 136 | */ 137 | 138 | float[] startP = GeomUtil.pointOnCircumference(centerX, centerY, angle - (mAsStar ? halfStep : angleStep), mAsStar ? radius * mStarRatio : radius); 139 | float[] centerP = GeomUtil.pointOnCircumference(centerX, centerY, angle, radius); 140 | float[] endP = GeomUtil.pointOnCircumference(centerX, centerY, angle + (mAsStar ? halfStep : angleStep), mAsStar ? radius * mStarRatio : radius); 141 | 142 | float[] bezierStart = GeomUtil.pointOnLine(centerP[0], centerP[1], startP[0], startP[1], this.mCornerRadius); 143 | float[] bezierEnd = GeomUtil.pointOnLine(centerP[0], centerP[1], endP[0], endP[1], this.mCornerRadius); 144 | 145 | float[] nextStart = GeomUtil.pointOnLine(endP[0], endP[1], centerP[0], centerP[1], this.mCornerRadius); 146 | 147 | starPath.moveTo(bezierStart[0], bezierStart[1]); 148 | starPath.quadTo(centerP[0], centerP[1], bezierEnd[0], bezierEnd[1]); 149 | starPath.lineTo(nextStart[0], nextStart[1]); 150 | 151 | int cpIndex = 0; 152 | 153 | mControlPoints.add(new PointF(bezierStart[0], bezierStart[1])); 154 | mControlPoints.add(new PointF(centerP[0], centerP[1])); 155 | mControlPoints.add(new PointF(bezierEnd[0], bezierEnd[1])); 156 | 157 | if(mAsStar){ 158 | cpIndex += 3; 159 | centerP = endP.clone(); 160 | endP = GeomUtil.pointOnCircumference(centerX, centerY, angle + angleStep, radius); 161 | 162 | bezierEnd = GeomUtil.pointOnLine(centerP[0], centerP[1], endP[0], endP[1], this.mCornerRadius); 163 | 164 | mControlPoints.add(new PointF(nextStart[0], nextStart[1])); 165 | 166 | nextStart = GeomUtil.pointOnLine(endP[0], endP[1], centerP[0], centerP[1], this.mCornerRadius); 167 | 168 | starPath.quadTo(centerP[0], centerP[1], bezierEnd[0], bezierEnd[1]); 169 | starPath.lineTo(nextStart[0], nextStart[1]); 170 | 171 | mControlPoints.add(new PointF(centerP[0], centerP[1])); 172 | mControlPoints.add(new PointF(bezierEnd[0], bezierEnd[1])); 173 | } 174 | 175 | for(int i = 0; i < mVertexCount - 1; i++){ 176 | cpIndex += 3; 177 | 178 | angle += angleStep; 179 | centerP = endP.clone(); 180 | endP = GeomUtil.pointOnCircumference(centerX, centerY, angle + (mAsStar ? halfStep : angleStep), mAsStar ? radius * mStarRatio : radius); 181 | 182 | bezierEnd = GeomUtil.pointOnLine(centerP[0], centerP[1], endP[0], endP[1], this.mCornerRadius); 183 | 184 | mControlPoints.add(new PointF(nextStart[0], nextStart[1])); 185 | 186 | nextStart = GeomUtil.pointOnLine(endP[0], endP[1], centerP[0], centerP[1], this.mCornerRadius); 187 | 188 | starPath.quadTo(centerP[0], centerP[1], bezierEnd[0], bezierEnd[1]); 189 | starPath.lineTo(nextStart[0], nextStart[1]); 190 | 191 | 192 | mControlPoints.add(new PointF(centerP[0], centerP[1])); 193 | mControlPoints.add(new PointF(bezierEnd[0], bezierEnd[1])); 194 | 195 | if(mAsStar){ 196 | cpIndex += 3; 197 | centerP = endP; 198 | endP = GeomUtil.pointOnCircumference(centerX, centerY, angle + angleStep, radius); 199 | 200 | bezierEnd = GeomUtil.pointOnLine(centerP[0], centerP[1], endP[0], endP[1], this.mCornerRadius); 201 | 202 | mControlPoints.add(new PointF(nextStart[0], nextStart[1])); 203 | 204 | nextStart = GeomUtil.pointOnLine(endP[0], endP[1], centerP[0], centerP[1], this.mCornerRadius); 205 | 206 | starPath.quadTo(centerP[0], centerP[1], bezierEnd[0], bezierEnd[1]); 207 | starPath.lineTo(nextStart[0], nextStart[1]); 208 | 209 | mControlPoints.add(new PointF(centerP[0], centerP[1])); 210 | mControlPoints.add(new PointF(bezierEnd[0], bezierEnd[1])); 211 | } 212 | } 213 | 214 | starPath.close(); 215 | } 216 | } 217 | 218 | public PointF[] getControlPoints(){ 219 | return mControlPoints.toArray(new PointF[mControlPoints.size()]); 220 | } 221 | 222 | public Path getOutline(){ 223 | return starPath; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/utils/GeomUtil.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.utils; 2 | 3 | import android.graphics.Matrix; 4 | import android.graphics.Rect; 5 | 6 | /** 7 | * Utility for geometry calculation 8 | * 9 | * Created by 7heaven on 16/8/5. 10 | */ 11 | public class GeomUtil { 12 | 13 | /** 14 | * Get the position on line(x0,y0-x1,y1) based on ratio 15 | * 16 | * @param x0 17 | * @param y0 18 | * @param x1 19 | * @param y1 20 | * @param ratio 21 | * @return position on line based on ratio float[]{x, y} 22 | */ 23 | public static float[] pointOnLine(float x0, float y0, float x1, float y1, float ratio){ 24 | float[] result = new float[2]; 25 | 26 | pointOnLine(x0, y0, x1, y1, ratio, result); 27 | 28 | return result; 29 | } 30 | 31 | public static void pointOnLine(float x0, float y0, float x1, float y1, float ratio, float[] position){ 32 | if(position != null && position.length == 2){ 33 | float dx = x1 - x0; 34 | float dy = y1 - y0; 35 | 36 | dx *= ratio; 37 | dy *= ratio; 38 | 39 | position[0] = x0 + dx; 40 | position[1] = y0 + dy; 41 | } 42 | } 43 | 44 | /** 45 | * Calculate point on circumference for circle(centerX, centerY, radius) with angle in radians 46 | * 47 | * @param centerX x of the center of the circle 48 | * @param centerY y of the center of the circle 49 | * @param angle angle in radians 50 | * @param radius radius of the circle 51 | * @return point on circumference float[]{x, y} 52 | */ 53 | public static float[] pointOnCircumference(int centerX, int centerY, double angle, double radius){ 54 | float[] result = new float[2]; 55 | 56 | pointOnCircumference(centerX, centerY, angle, radius, result); 57 | 58 | return result; 59 | } 60 | 61 | public static void pointOnCircumference(int centerX, int centerY, double angle, double radius, float[] outPoint){ 62 | if(outPoint != null && outPoint.length == 2){ 63 | float x = (float) (radius * Math.cos(angle) + centerX); 64 | float y = (float) (radius * Math.sin(angle) + centerY); 65 | 66 | outPoint[0] = x; 67 | outPoint[1] = y; 68 | } 69 | } 70 | 71 | /** 72 | * Get the transformation matrix from the original rectangle to the target rectangle 73 | * 74 | * @param originalRect 75 | * @param targetRect 76 | * @param outMatrix 77 | */ 78 | public static void getTransformationMatrix(Rect originalRect, Rect targetRect, Matrix outMatrix){ 79 | if(outMatrix != null){ 80 | int transformX = targetRect.left - originalRect.left; 81 | int transformY = targetRect.top - originalRect.top; 82 | 83 | float scaleX = (float) targetRect.width() / (float) originalRect.width(); 84 | float scaleY = (float) targetRect.height() / (float) originalRect.height(); 85 | 86 | outMatrix.setScale(scaleX, scaleY, originalRect.left, originalRect.top); 87 | outMatrix.postTranslate(transformX, transformY); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/utils/PathMeasurement.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.utils; 2 | 3 | import android.graphics.Path; 4 | import android.graphics.PathMeasure; 5 | import android.support.annotation.FloatRange; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * 12 | * Convenient way for Path calculation when Path contain multiple contours 13 | * 14 | * Created by 7heaven on 16/8/8. 15 | */ 16 | public class PathMeasurement { 17 | 18 | /** 19 | * Main path for measurement 20 | */ 21 | private Path mPath; 22 | 23 | /** 24 | * Sub paths and stored parameters for calculation 25 | */ 26 | private Path[] mSubPaths; 27 | private Path[] mSubPathsOutputStore; 28 | private Path[] mSubPathsOutput; 29 | private float[] mSubPathLengths; 30 | private float[] mSubPathPercentageRanges; 31 | 32 | /** 33 | * Total length of the main path 34 | */ 35 | private float mTotalLength; 36 | 37 | /** 38 | * PathMeasure instance for all calculations 39 | */ 40 | private PathMeasure mPathMeasure; 41 | 42 | public PathMeasurement(Path path){ 43 | mPath = path; 44 | 45 | mPathMeasure = new PathMeasure(); 46 | 47 | initPathCalculation(); 48 | } 49 | 50 | public void setPath(Path path){ 51 | mPath = path; 52 | 53 | initPathCalculation(); 54 | } 55 | 56 | private void initPathCalculation(){ 57 | if(mPath != null){ 58 | //ensure PathMeasure position at the first contour 59 | mPathMeasure.setPath(mPath, false); 60 | mTotalLength = 0; 61 | 62 | List subPathLengths = new ArrayList<>(); 63 | List subPaths = new ArrayList<>(); 64 | 65 | do{ 66 | Path path = new Path(); 67 | float sublength = mPathMeasure.getLength(); 68 | mTotalLength += sublength; 69 | mPathMeasure.getSegment(0, sublength, path, true); 70 | subPathLengths.add(sublength); 71 | subPaths.add(path); 72 | }while(mPathMeasure.nextContour()); 73 | 74 | final int totalContour = subPathLengths.size(); 75 | 76 | mSubPathsOutputStore = new Path[totalContour]; 77 | mSubPaths = new Path[totalContour]; 78 | mSubPathLengths = new float[totalContour]; 79 | mSubPathsOutput = new Path[totalContour]; 80 | mSubPathPercentageRanges = new float[totalContour * 3]; 81 | 82 | float percentageStep = 0; 83 | for(int i = 0; i < totalContour; i++){ 84 | mSubPaths[i] = subPaths.get(i); 85 | mSubPathsOutputStore[i] = new Path(); 86 | mSubPathLengths[i] = subPathLengths.get(i); 87 | 88 | float singlePercentage = mSubPathLengths[i] / mTotalLength; 89 | 90 | //sub path start position 91 | mSubPathPercentageRanges[i * 3] = percentageStep; 92 | //sub path end position 93 | mSubPathPercentageRanges[i * 3 + 1] = percentageStep + singlePercentage; 94 | mSubPathPercentageRanges[i * 3 + 2] = mTotalLength / mSubPathLengths[i]; 95 | 96 | percentageStep += singlePercentage; 97 | } 98 | 99 | 100 | mPathMeasure.setPath(mPath, false); 101 | } 102 | } 103 | 104 | /** 105 | * Get all the sub paths of the main Path 106 | * @return 107 | */ 108 | public Path[] getAllSubPaths(){ 109 | return mSubPaths; 110 | } 111 | 112 | /** 113 | * Get position and tangent values based on percentage mapping to the entire Path 114 | * @param percentage 115 | * @param pos 116 | * @param tan 117 | * @return 118 | */ 119 | public float getPosTan(@FloatRange(from=0, to=1.0) float percentage, float[] pos, float[] tan){ 120 | return getPosTan(percentage, pos, tan, false, 0); 121 | } 122 | 123 | /** 124 | * Get position and tangent values based on percentage, this is useful when Path contains more than one contour 125 | * @param percentage 126 | * @param pos 127 | * @param tan 128 | * @param async indicate whether percentage should apply to the entire Path length or apply to each sub path 129 | * @param contourNum this parameter will work only when asyn == true, indicate which contour should be the target to return position and tangent 130 | * @return 131 | */ 132 | public float getPosTan(@FloatRange(from=0, to=1.0) float percentage, float[] pos, float[] tan, final boolean async, int contourNum){ 133 | float distance = percentage * mTotalLength; 134 | 135 | if(pos != null && tan != null && pos.length == 2 && tan.length == 2){ 136 | mPathMeasure.setPath(mPath, false); 137 | 138 | int i = 0; 139 | do{ 140 | if(async){ 141 | if(i == contourNum){ 142 | mPathMeasure.getPosTan(mSubPathLengths[i] * percentage, pos, tan); 143 | break; 144 | } 145 | }else{ 146 | float subPathStart = mSubPathPercentageRanges[i * 3]; 147 | float subPathEnd = mSubPathPercentageRanges[i * 3 + 1]; 148 | float multiples = mSubPathPercentageRanges[i * 3 + 2]; 149 | 150 | if(subPathStart < percentage && subPathEnd >= percentage){ 151 | float subDistance = subPathEnd >= percentage ? (percentage - subPathStart) * multiples * mSubPathLengths[i] : mSubPathLengths[i]; 152 | 153 | mPathMeasure.getPosTan(subDistance, pos, tan); 154 | }else if(subPathStart >= percentage){ 155 | break; 156 | } 157 | } 158 | 159 | i++; 160 | }while(i < mSubPathsOutputStore.length && mPathMeasure.nextContour()); 161 | 162 | mPathMeasure.setPath(mPath, false); 163 | } 164 | 165 | return distance; 166 | } 167 | 168 | /** 169 | * Return sub paths based on percentage, notice that the return array with always be same length which is the sub-path count of the main path, 170 | * so for those sub-paths not covered by percentage will return null; 171 | * @param percentage 172 | * @param async indicate whether percentage should apply to the entire Path length or apply to each sub path 173 | * @return 174 | */ 175 | public Path[] updatePhare(@FloatRange(from=0, to=1.0) float percentage, final boolean async){ 176 | if(mSubPathsOutputStore != null){ 177 | 178 | mPathMeasure.setPath(mPath, false); 179 | 180 | int i = 0; 181 | do{ 182 | mSubPathsOutputStore[i].reset(); 183 | 184 | if(async){ 185 | boolean success = mPathMeasure.getSegment(0, mSubPathLengths[i] * percentage, mSubPathsOutputStore[i], true); 186 | if(success){ 187 | // On KITKAT and earlier releases, the resulting path may not display on a hardware-accelerated Canvas. A simple workaround is to add a single operation to this path 188 | // @see https://developer.android.com/reference/android/graphics/PathMeasure.html#getSegment(float, float, android.graphics.Path, boolean) 189 | mSubPathsOutputStore[i].rLineTo(0, 0); 190 | mSubPathsOutput[i] = mSubPathsOutputStore[i]; 191 | }else{ 192 | mSubPathsOutput[i] = null; 193 | } 194 | }else{ 195 | float subPathStart = mSubPathPercentageRanges[i * 3]; 196 | float subPathEnd = mSubPathPercentageRanges[i * 3 + 1]; 197 | float multiples = mSubPathPercentageRanges[i * 3 + 2]; 198 | 199 | if(subPathStart >= percentage){ 200 | mSubPathsOutput[i] = null; 201 | }else{ 202 | 203 | float endD = subPathEnd >= percentage ? (percentage - subPathStart) * multiples * mSubPathLengths[i] : mSubPathLengths[i]; 204 | 205 | boolean success = mPathMeasure.getSegment(0, endD, mSubPathsOutputStore[i], true); 206 | if(success){ 207 | // On KITKAT and earlier releases, the resulting path may not display on a hardware-accelerated Canvas. A simple workaround is to add a single operation to this path 208 | // @see https://developer.android.com/reference/android/graphics/PathMeasure.html#getSegment(float, float, android.graphics.Path, boolean) 209 | mSubPathsOutputStore[i].rLineTo(0, 0); 210 | mSubPathsOutput[i] = mSubPathsOutputStore[i]; 211 | }else{ 212 | mSubPathsOutput[i] = null; 213 | } 214 | } 215 | } 216 | 217 | i++; 218 | 219 | }while(i < mSubPathsOutputStore.length && mPathMeasure.nextContour()); 220 | 221 | mPathMeasure.setPath(mPath, false); 222 | 223 | return mSubPathsOutput; 224 | } 225 | 226 | return null; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/views/AnimatedImageView.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.views; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Matrix; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.view.animation.DecelerateInterpolator; 11 | import android.widget.ImageView; 12 | 13 | /** 14 | * A ImageView that animated scaleType change 15 | * 16 | * Created by 7heaven on 16/7/30. 17 | */ 18 | public class AnimatedImageView extends ImageView { 19 | 20 | private Matrix mProgressiveMatrix; 21 | private float[] mInitialMatrixValues; 22 | private float[] mProgressingMatrixValues; 23 | private float[] mTargetMatrixValues; 24 | 25 | private ValueAnimator mAnimator; 26 | 27 | private boolean mAnimationMark = false; 28 | private boolean mIgnoreMatrixAnimation = true; 29 | 30 | public AnimatedImageView(Context context){ 31 | this(context, null); 32 | } 33 | 34 | public AnimatedImageView(Context context, AttributeSet attrs){ 35 | this(context, attrs, 0); 36 | } 37 | 38 | public AnimatedImageView(Context context, AttributeSet attrs, int defStyle){ 39 | super(context, attrs, defStyle); 40 | 41 | Matrix targetMatrix = getImageMatrix(); 42 | mProgressiveMatrix = new Matrix(targetMatrix); 43 | mInitialMatrixValues = new float[9]; 44 | mProgressingMatrixValues = new float[9]; 45 | mTargetMatrixValues = new float[9]; 46 | 47 | mAnimator = ValueAnimator.ofFloat(0, 1); 48 | mAnimator.setDuration(500); 49 | mAnimator.setInterpolator(new DecelerateInterpolator()); 50 | mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 51 | @Override 52 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 53 | float value = (float) valueAnimator.getAnimatedValue(); 54 | updateProgressiveMatrix(value); 55 | } 56 | }); 57 | } 58 | 59 | @Override 60 | public void setScaleType(ScaleType scaleType){ 61 | super.setScaleType(scaleType); 62 | 63 | mIgnoreMatrixAnimation = true; 64 | } 65 | 66 | /** 67 | * setScaleType with animated transformation 68 | * @param scaleType 69 | */ 70 | public void setScaleTypeAnimated(ScaleType scaleType){ 71 | super.setScaleType(scaleType); 72 | 73 | mIgnoreMatrixAnimation = false; 74 | } 75 | 76 | @Override 77 | protected void onLayout(boolean changed, int left, int top, int right, int bottom){ 78 | Matrix targetMatrix = getImageMatrix(); 79 | 80 | if(targetMatrix != null){ 81 | if(!targetMatrix.equals(mProgressiveMatrix)){ 82 | if(mIgnoreMatrixAnimation){ 83 | mProgressiveMatrix = new Matrix(targetMatrix); 84 | }else{ 85 | mAnimationMark = true; 86 | } 87 | } 88 | } 89 | } 90 | 91 | private void startProgressiveMatrixTransform(){ 92 | if(mAnimator.isRunning()) mAnimator.end(); 93 | 94 | Matrix targetMatrix = getImageMatrix(); 95 | if(targetMatrix != null){ 96 | targetMatrix.getValues(mTargetMatrixValues); 97 | } 98 | 99 | if(mProgressiveMatrix != null){ 100 | mProgressiveMatrix.getValues(mInitialMatrixValues); 101 | } 102 | 103 | for(int i = 0; i < 9; i++){ 104 | mProgressingMatrixValues[i] = 0; 105 | } 106 | 107 | mAnimator.start(); 108 | } 109 | 110 | private void updateProgressiveMatrix(float progress){ 111 | for(int i = 0; i < 9; i++){ 112 | float dValue = mTargetMatrixValues[i] - mInitialMatrixValues[i]; 113 | mProgressingMatrixValues[i] = dValue * progress + mInitialMatrixValues[i]; 114 | } 115 | 116 | mProgressiveMatrix.setValues(mProgressingMatrixValues); 117 | 118 | invalidate(); 119 | } 120 | 121 | @Override 122 | public void onDraw(Canvas canvas){ 123 | if(mAnimationMark){ 124 | startProgressiveMatrixTransform(); 125 | mAnimationMark = false; 126 | } 127 | 128 | Drawable drawable = getDrawable(); 129 | if(drawable != null){ 130 | int saveCount = canvas.getSaveCount(); 131 | canvas.save(); 132 | boolean cropToPadding = Build.VERSION.SDK_INT > 16 && getCropToPadding(); 133 | if(cropToPadding){ 134 | final int scrollX = getScrollX(); 135 | final int scrollY = getScrollY(); 136 | 137 | canvas.clipRect(scrollX + getPaddingLeft(), scrollY + getPaddingTop(), 138 | scrollX + getRight() - getLeft() - getPaddingRight(), 139 | scrollY + getBottom() - getTop() - getPaddingBottom()); 140 | } 141 | 142 | canvas.translate(getPaddingLeft(), getPaddingTop()); 143 | 144 | if(mProgressiveMatrix != null){ 145 | canvas.concat(mProgressiveMatrix); 146 | } 147 | 148 | drawable.draw(canvas); 149 | canvas.restoreToCount(saveCount); 150 | } 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/views/FitWidthImageView.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Matrix; 6 | import android.graphics.drawable.Drawable; 7 | import android.util.AttributeSet; 8 | import android.widget.ImageView; 9 | 10 | /** 11 | * TODO reconstrution 12 | * Created by 7heaven on 16/8/9. 13 | */ 14 | public class FitWidthImageView extends ImageView { 15 | 16 | private int width; 17 | private int height; 18 | 19 | private int intrinsicWidth; 20 | private int intrinsicHeight; 21 | 22 | private float scaleRate; 23 | 24 | public FitWidthImageView(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public FitWidthImageView(Context context, AttributeSet attrs) { 29 | this(context, attrs, 0); 30 | } 31 | 32 | public FitWidthImageView(Context context, AttributeSet attrs, int defStyle) { 33 | super(context, attrs, defStyle); 34 | setScaleType(ScaleType.MATRIX); 35 | } 36 | 37 | @Override 38 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 39 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 40 | 41 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 42 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 43 | 44 | 45 | width = getMeasuredWidth(); 46 | 47 | if(heightMode == MeasureSpec.EXACTLY){ 48 | height = heightSize; 49 | }else{ 50 | height = heightSize - getPaddingTop() - getPaddingBottom(); 51 | } 52 | 53 | float rate = (float) width / (float) intrinsicWidth; 54 | height = (int) ((float) intrinsicHeight * rate); 55 | setMeasuredDimension(width, height); 56 | 57 | scaleToFit(false); 58 | } 59 | 60 | 61 | 62 | @Override 63 | protected void onSizeChanged(int w, int h, int oldw, int oldh){ 64 | super.onSizeChanged(w, h, oldw, oldh); 65 | 66 | width = w; 67 | height = h; 68 | } 69 | 70 | @Override 71 | public void setImageDrawable(Drawable drawable) { 72 | free(); 73 | if (drawable != null) { 74 | intrinsicWidth = drawable.getIntrinsicWidth(); 75 | intrinsicHeight = drawable.getIntrinsicHeight(); 76 | super.setImageDrawable(drawable); 77 | scaleToFit(); 78 | } else { 79 | super.setImageDrawable(null); 80 | } 81 | } 82 | 83 | @Override 84 | public void setImageResource(int resourceId) { 85 | free(); 86 | super.setImageResource(resourceId); 87 | 88 | Drawable d = this.getDrawable(); 89 | 90 | if (d != null) { 91 | intrinsicWidth = d.getIntrinsicWidth(); 92 | intrinsicHeight = d.getIntrinsicHeight(); 93 | scaleToFit(); 94 | } 95 | } 96 | 97 | @Override 98 | public void setImageBitmap(Bitmap bitmap) { 99 | free(); 100 | if (bitmap != null) { 101 | intrinsicWidth = bitmap.getWidth(); 102 | intrinsicHeight = bitmap.getHeight(); 103 | super.setImageBitmap(bitmap); 104 | scaleToFit(); 105 | } else { 106 | super.setImageBitmap(null); 107 | } 108 | } 109 | 110 | public float getScaleRate() { 111 | return scaleRate; 112 | } 113 | 114 | public int getIntrinsicWidth() { 115 | return intrinsicWidth; 116 | } 117 | 118 | public int getIntrinsicHeight() { 119 | return intrinsicHeight; 120 | } 121 | 122 | @Override 123 | public void onDetachedFromWindow() { 124 | super.onDetachedFromWindow(); 125 | free(); 126 | } 127 | 128 | private void free() { 129 | if (getDrawingCache() != null) { 130 | Bitmap b = getDrawingCache(); 131 | setImageBitmap(null); 132 | b.recycle(); 133 | b = null; 134 | } 135 | 136 | } 137 | 138 | private void scaleToFit() { 139 | scaleToFit(true); 140 | } 141 | 142 | private void scaleToFit(boolean forceLayout){ 143 | Matrix matrix = new Matrix(); 144 | float rate = (float) width / (float) intrinsicWidth; 145 | scaleRate = rate; 146 | matrix.postScale(rate, rate); 147 | setImageMatrix(matrix); 148 | if(forceLayout) requestLayout(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/views/GroupedAvatarImageView.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.drawable.Drawable; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.ImageView; 9 | 10 | import com.sevenheaven.uilibrary.drawables.GroupedAvatarDrawable; 11 | 12 | import java.security.acl.Group; 13 | 14 | /** 15 | * Created by 7heaven on 16/8/13. 16 | */ 17 | public class GroupedAvatarImageView extends ImageView { 18 | 19 | private GroupedAvatarDrawable mDrawable; 20 | 21 | public GroupedAvatarImageView(Context context){ 22 | this(context, null); 23 | } 24 | 25 | public GroupedAvatarImageView(Context context, AttributeSet attrs){ 26 | this(context, attrs, 0); 27 | } 28 | 29 | public GroupedAvatarImageView(Context context, AttributeSet attrs, int defStyle){ 30 | super(context, attrs, defStyle); 31 | } 32 | 33 | public void setAvatars(Bitmap... bitmaps){ 34 | 35 | } 36 | 37 | @Override 38 | public void setImageDrawable(Drawable drawable){ 39 | if(drawable instanceof GroupedAvatarDrawable){ 40 | super.setImageDrawable(drawable); 41 | mDrawable = (GroupedAvatarDrawable) drawable; 42 | } 43 | } 44 | 45 | @Override 46 | public void setImageBitmap(Bitmap bitmap){ 47 | super.setImageBitmap(bitmap); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /uilibrary/src/main/java/com/sevenheaven/uilibrary/views/PageIndicator.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary.views; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.View; 10 | 11 | import com.sevenheaven.uilibrary.R; 12 | 13 | /** 14 | * PageIndicator that can easily customize contents, with many pre-defined style such as circle, square, alphabet, etc. 15 | * Created by 7heaven on 16/5/25. 16 | */ 17 | public class PageIndicator extends View { 18 | 19 | public enum BlockType{ 20 | 21 | SQUARE(0), 22 | CIRCLE(1), 23 | VERTICAL_LINE(2), 24 | HORIZONTAL_LINE(3), 25 | ALPHABET(4), 26 | NUMMERIC(5), 27 | CUSTOM_CHAR(6); 28 | 29 | private int mValue; 30 | 31 | BlockType(int value){ 32 | mValue = value; 33 | } 34 | } 35 | 36 | private BlockType mCurrentBlockType; 37 | private int mBlockGap; 38 | private int mBlockSize; 39 | private int mBlockColor; 40 | private int mHighlightColor; 41 | 42 | private int mHalfBlockSize; 43 | 44 | private int mPageCount; 45 | private int mTotalContentWidth; 46 | 47 | /** 48 | * For recording content coordinate after measurement 49 | */ 50 | private int mContentX; 51 | private int mContentY; 52 | 53 | /** 54 | * Canvas's drawText do not accept single char, 55 | * so we define a char array here for char content drawing 56 | */ 57 | private char[] mAlphabetStore = new char[1]; 58 | private char[] mCustomChar; 59 | 60 | private int mCurrentSelection = 0; 61 | 62 | private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 63 | /** 64 | * FontMetric for positioning Text when drawText method is call 65 | */ 66 | private Paint.FontMetrics mAlphabetFM = new Paint.FontMetrics(); 67 | 68 | public PageIndicator(Context context){ 69 | this(context, null); 70 | } 71 | 72 | public PageIndicator(Context context, AttributeSet attrs){ 73 | this(context, attrs, 0); 74 | } 75 | 76 | public PageIndicator(Context context, AttributeSet attrs, int defStyle){ 77 | super(context, attrs, defStyle); 78 | 79 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PageIndicator); 80 | setBlockType(BlockType.values()[ta.getInt(R.styleable.PageIndicator_blockType, BlockType.CIRCLE.mValue)]); 81 | setBlockSize(ta.getDimensionPixelSize(R.styleable.PageIndicator_blockSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, context.getResources().getDisplayMetrics()))); 82 | setBlockGap(ta.getDimensionPixelSize(R.styleable.PageIndicator_gap, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics()))); 83 | setBlockColor(ta.getColor(R.styleable.PageIndicator_blockColor, 0xFFF5F5F5)); 84 | setHighlightColor(ta.getColor(R.styleable.PageIndicator_highlightColor, 0xFFFFFFFF)); 85 | setTotalPageCount(ta.getInt(R.styleable.PageIndicator_totalCount, 3)); 86 | 87 | String customChars = ta.getString(R.styleable.PageIndicator_customChar); 88 | if(customChars != null) setCustomChar(customChars.toCharArray()); 89 | 90 | ta.recycle(); 91 | 92 | } 93 | 94 | public void setCustomChar(char... customChar){ 95 | mCustomChar = customChar; 96 | if(mCurrentBlockType == BlockType.CUSTOM_CHAR){ 97 | invalidate(); 98 | } 99 | } 100 | 101 | public void setTotalPageCount(int pageCount){ 102 | if(mPageCount != pageCount){ 103 | mPageCount = pageCount; 104 | 105 | requestLayout(); 106 | } 107 | } 108 | 109 | public void setCurrentSelection(int current){ 110 | if(current != mCurrentSelection){ 111 | mCurrentSelection = current; 112 | invalidate(); 113 | } 114 | } 115 | 116 | public void setBlockType(BlockType type){ 117 | mCurrentBlockType = type; 118 | } 119 | 120 | public void setBlockSize(int size){ 121 | if(mBlockSize != size){ 122 | mBlockSize = size; 123 | 124 | mHalfBlockSize = size / 2; 125 | 126 | //for convenient when BlockType being set to ALPHABET 127 | mPaint.setTextSize(size); 128 | mPaint.getFontMetrics(mAlphabetFM); 129 | 130 | requestLayout(); 131 | } 132 | } 133 | 134 | public void setBlockGap(int gap){ 135 | if(mBlockGap != gap){ 136 | mBlockGap = gap; 137 | 138 | requestLayout(); 139 | } 140 | } 141 | 142 | public void setBlockColor(int blockColor){ 143 | if(mBlockColor != blockColor){ 144 | mBlockColor = blockColor; 145 | 146 | invalidate(); 147 | } 148 | } 149 | 150 | public void setHighlightColor(int highlightColor){ 151 | if(mHighlightColor != highlightColor){ 152 | mHighlightColor = highlightColor; 153 | 154 | invalidate(); 155 | } 156 | } 157 | 158 | @Override 159 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ 160 | if(mPageCount > 0){ 161 | /** 162 | * calculate minimum content width based on block size and block gap 163 | */ 164 | mTotalContentWidth = mPageCount * mBlockSize + (mPageCount - 1) * mBlockGap; 165 | 166 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 167 | final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 168 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 169 | final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 170 | 171 | int width; 172 | int height; 173 | 174 | if(widthMode == MeasureSpec.EXACTLY){ 175 | width = widthSize; 176 | }else{ 177 | width = mTotalContentWidth; 178 | } 179 | 180 | if(heightMode == MeasureSpec.EXACTLY){ 181 | height = heightSize; 182 | }else{ 183 | height = mBlockSize; 184 | } 185 | 186 | setMeasuredDimension(width, height); 187 | }else{ 188 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 189 | } 190 | } 191 | 192 | @Override 193 | protected void onSizeChanged(int w, int h, int oldw, int oldh){ 194 | int cX = w / 2; 195 | int cY = h / 2; 196 | 197 | mContentX = cX - mTotalContentWidth / 2; 198 | mContentY = cY - mHalfBlockSize; 199 | } 200 | 201 | @Override 202 | public void onDraw(Canvas canvas){ 203 | int stepX = mContentX; 204 | for(int i = 0; i < mPageCount; i++){ 205 | if(i == mCurrentSelection){ 206 | mPaint.setColor(mHighlightColor); 207 | }else{ 208 | mPaint.setColor(mBlockColor); 209 | } 210 | 211 | drawContent(canvas, stepX, mContentY, i); 212 | stepX += mBlockSize + mBlockGap; 213 | } 214 | } 215 | 216 | private void drawContent(Canvas canvas, int x, int y, int index){ 217 | switch(mCurrentBlockType){ 218 | case SQUARE: 219 | canvas.drawRect(x, y, x + mBlockSize, y + mBlockSize, mPaint); 220 | break; 221 | case CIRCLE: 222 | canvas.drawCircle(x + mHalfBlockSize, y + mHalfBlockSize, mHalfBlockSize, mPaint); 223 | break; 224 | case VERTICAL_LINE: 225 | canvas.drawLine(x + mHalfBlockSize, y, x + mHalfBlockSize, y + mBlockSize, mPaint); 226 | break; 227 | case HORIZONTAL_LINE: 228 | canvas.drawLine(x, y + mHalfBlockSize, x + mBlockSize, y + mHalfBlockSize, mPaint); 229 | break; 230 | case ALPHABET: 231 | mAlphabetStore[0] = (char) ((index % 26) + 'A'); 232 | canvas.drawText(mAlphabetStore, 0, 1, x, y + mBlockSize - mAlphabetFM.descent, mPaint); 233 | break; 234 | case NUMMERIC: 235 | mAlphabetStore[0] = (char) ((index % 9) + '1'); 236 | canvas.drawText(mAlphabetStore, 0, 1, x, y + mBlockSize - mAlphabetFM.descent, mPaint); 237 | break; 238 | case CUSTOM_CHAR: 239 | if(mCustomChar != null){ 240 | mAlphabetStore[0] = mCustomChar[(index % mCustomChar.length)]; 241 | canvas.drawText(mAlphabetStore, 0, 1, x, y + mBlockSize - mAlphabetFM.descent, mPaint); 242 | } 243 | break; 244 | 245 | } 246 | } 247 | } 248 | 249 | -------------------------------------------------------------------------------- /uilibrary/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 | -------------------------------------------------------------------------------- /uilibrary/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | uilibrary 3 | 4 | -------------------------------------------------------------------------------- /uilibrary/src/test/java/com/sevenheaven/uilibrary/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.sevenheaven.uilibrary; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } --------------------------------------------------------------------------------