├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── wang │ │ └── raye │ │ └── myview │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── wang │ │ │ └── raye │ │ │ │ └── myview │ │ │ │ ├── AnimActivity.java │ │ │ │ ├── AsyncTastActivity.java │ │ │ │ ├── BinderActivity.java │ │ │ │ ├── CollapsedActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MoreTextViewActivity.java │ │ │ │ ├── NonRecursiveActivity.java │ │ │ │ └── services │ │ │ │ └── BinderService.java │ │ └── widget │ │ │ ├── AbstractPathAnimator.java │ │ │ ├── HeartLayout.java │ │ │ ├── HeartView.java │ │ │ └── PathAnimator.java │ └── res │ │ ├── anim │ │ └── flower.xml │ │ ├── layout │ │ ├── activity_anim.xml │ │ ├── activity_async_tast.xml │ │ ├── activity_binder.xml │ │ ├── activity_collapsed.xml │ │ ├── activity_main.xml │ │ ├── activity_more.xml │ │ └── activity_non_recursive.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── heart.png │ │ ├── heart_border.png │ │ ├── ic_flower.png │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── wang │ └── raye │ └── myview │ └── ExampleUnitTest.java ├── build.gradle ├── device-2016-06-29-225142.mp4_1467212082.gif ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── wang │ │ └── raye │ │ └── library │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── wang │ │ │ └── raye │ │ │ └── library │ │ │ └── widge │ │ │ ├── CollapsedTextView.java │ │ │ └── MoreTextView.java │ └── res │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── wang │ └── raye │ └── library │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | MyView -------------------------------------------------------------------------------- /.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 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #MoreTextView 全文收起TextView(控件嵌套版) 2 | ![预览图](https://github.com/RayeWang/MyView/blob/master/device-2016-06-29-225142.mp4_1467212082.gif) 3 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "wang.raye.myview" 9 | minSdkVersion 15 10 | targetSdkVersion 23 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(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | compile 'wang.raye.preioc:preioccore:1.0.6' 27 | compile project(':library') 28 | } 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\CODE\andridsdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/wang/raye/myview/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 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 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 24 | 27 | 30 | 31 | 34 | 36 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/AnimActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.view.animation.Animation; 8 | import android.view.animation.AnimationSet; 9 | import android.view.animation.AnimationUtils; 10 | import android.view.animation.ScaleAnimation; 11 | import android.view.animation.TranslateAnimation; 12 | import android.widget.Button; 13 | import android.widget.ImageView; 14 | 15 | import java.util.Random; 16 | import java.util.Timer; 17 | import java.util.TimerTask; 18 | 19 | import wang.raye.preioc.PreIOC; 20 | import wang.raye.preioc.annotation.BindById; 21 | import wang.raye.preioc.annotation.OnClick; 22 | import widget.HeartLayout; 23 | 24 | public class AnimActivity extends AppCompatActivity { 25 | 26 | @BindById(R.id.iv_destination) 27 | ImageView ivDestination; 28 | @BindById(R.id.iv_source) 29 | ImageView ivSource; 30 | @BindById(R.id.btn_begin) 31 | Button btnBegin; 32 | @BindById(R.id.heart_layout) 33 | HeartLayout heartLayout; 34 | 35 | private Random mRandom = new Random(); 36 | private Timer mTimer = new Timer(); 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_anim); 41 | PreIOC.binder(this); 42 | mTimer.scheduleAtFixedRate(new TimerTask() { 43 | @Override 44 | public void run() { 45 | heartLayout.post(new Runnable() { 46 | @Override 47 | public void run() { 48 | heartLayout.addImage(R.mipmap.ic_flower); 49 | } 50 | }); 51 | } 52 | }, 500, 200); 53 | } 54 | 55 | @OnClick(R.id.btn_begin) 56 | public void onClick(View view) { 57 | Animation animation = AnimationUtils.loadAnimation(this, R.anim.flower); 58 | TranslateAnimation translateAnimation = new TranslateAnimation(0, (ivDestination.getX() - ivSource.getX()), 0, (ivDestination.getY() - ivSource.getY())); 59 | translateAnimation.setStartOffset(3000); 60 | translateAnimation.setDuration(2000); 61 | ScaleAnimation scaleAnimation = new ScaleAnimation(1f, 0.f, 1f, 0.f); 62 | scaleAnimation.setStartOffset(3000); 63 | scaleAnimation.setDuration(2000); 64 | 65 | 66 | AnimationSet set = new AnimationSet(false); 67 | set.addAnimation(animation); 68 | set.addAnimation(scaleAnimation); 69 | set.addAnimation(translateAnimation); 70 | 71 | Log.i("AnimActivity", "x:" + (ivDestination.getX() - ivSource.getX()) + " y:" + (ivDestination.getY() - ivSource.getY())); 72 | set.setAnimationListener(new Animation.AnimationListener() { 73 | @Override 74 | public void onAnimationStart(Animation animation) { 75 | ivSource.setVisibility(View.VISIBLE); 76 | } 77 | 78 | @Override 79 | public void onAnimationEnd(Animation animation) { 80 | ivSource.setVisibility(View.INVISIBLE); 81 | } 82 | 83 | @Override 84 | public void onAnimationRepeat(Animation animation) { 85 | 86 | } 87 | }); 88 | ivSource.startAnimation(set); 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/AsyncTastActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.Toast; 10 | 11 | import wang.raye.preioc.PreIOC; 12 | import wang.raye.preioc.annotation.BindById; 13 | import wang.raye.preioc.annotation.OnClick; 14 | 15 | /** 16 | * 测试AsyncTask的activity 17 | * Create by Raye on 2016年8月24日23:51:15 18 | */ 19 | public class AsyncTastActivity extends AppCompatActivity { 20 | private static final String TAG = AsyncTastActivity.class.getName(); 21 | @BindById(R.id.btn_close) 22 | Button btnClose; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_async_tast); 28 | PreIOC.binder(this); 29 | new TestTask().execute(); 30 | } 31 | 32 | @OnClick(R.id.btn_close) 33 | public void onClick(View view){ 34 | finish(); 35 | } 36 | public class TestTask extends AsyncTask{ 37 | 38 | @Override 39 | protected Boolean doInBackground(Void... params) { 40 | Log.i(TAG,"begin doInBackground"); 41 | try { 42 | Thread.sleep(10000); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | Log.i(TAG,"end doInBackground"); 47 | return true; 48 | } 49 | 50 | @Override 51 | protected void onPostExecute(Boolean aBoolean) { 52 | Log.i(TAG,"onPostExecute"); 53 | Toast.makeText(AsyncTastActivity.this, "onPostExecute", Toast.LENGTH_SHORT).show(); 54 | super.onPostExecute(aBoolean); 55 | } 56 | } 57 | 58 | @Override 59 | public void onLowMemory() { 60 | super.onLowMemory(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/BinderActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.ServiceConnection; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.os.IBinder; 10 | import android.os.Message; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.view.View; 13 | import android.widget.Button; 14 | import android.widget.TextView; 15 | 16 | import wang.raye.myview.services.BinderService; 17 | import wang.raye.preioc.PreIOC; 18 | import wang.raye.preioc.annotation.BindById; 19 | import wang.raye.preioc.annotation.OnClick; 20 | 21 | /** 22 | * 测试binder的activity 23 | * Create by Raye on 2016年8月24日16:32:00 24 | */ 25 | public class BinderActivity extends AppCompatActivity { 26 | 27 | @BindById(R.id.btn_start) 28 | Button btnStart; 29 | @BindById(R.id.tv_msg) 30 | TextView tvMsg; 31 | private ServiceConnection sc; 32 | private BinderService.MyBinder binder; 33 | 34 | private Handler handler = new Handler(){ 35 | @Override 36 | public void handleMessage(Message msg) { 37 | tvMsg.setText(""+System.currentTimeMillis()); 38 | } 39 | }; 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_binder); 44 | PreIOC.binder(this); 45 | sc = new ServiceConnection() { 46 | @Override 47 | public void onServiceConnected(ComponentName name, IBinder service) { 48 | binder = (BinderService.MyBinder) service; 49 | binder.setHandler(handler); 50 | } 51 | 52 | @Override 53 | public void onServiceDisconnected(ComponentName name) { 54 | 55 | } 56 | }; 57 | 58 | } 59 | 60 | @OnClick({R.id.btn_start,R.id.btn_stop}) 61 | public void click(View view){ 62 | switch (view.getId()){ 63 | case R.id.btn_start: 64 | Intent intent = new Intent(this, BinderService.class); 65 | bindService(intent,sc, Context.BIND_AUTO_CREATE); 66 | break; 67 | case R.id.btn_stop: 68 | unbindService(sc); 69 | break; 70 | } 71 | 72 | } 73 | 74 | 75 | @Override 76 | protected void onDestroy() { 77 | unbindService(sc); 78 | super.onDestroy(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/CollapsedActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import wang.raye.library.widge.CollapsedTextView; 7 | import wang.raye.preioc.PreIOC; 8 | import wang.raye.preioc.annotation.BindById; 9 | 10 | public class CollapsedActivity extends AppCompatActivity { 11 | 12 | @BindById(R.id.ctv_view) 13 | CollapsedTextView ctvView; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_collapsed); 19 | PreIOC.binder(this); 20 | ctvView.setShowText("因为公司项目需要全文收起的功能,一共有2种UI所以需要写2个全文收起的控件,之前也是用过一个全文收起的控件,但是因为设计原因,在ListView刷新的时候会闪烁,我估计原因是因为控件本身的设计是需要先让TextView绘制完成,然后获取TextView一共有多少行,再判断是否需要全文收起按钮,如果需要,则吧TextView压缩回最大行数,添加全文按钮,这样就会造成ListView的Item先高后低,所以会发生闪烁,后面我也在网上找了几个,发现和之前的设计都差不多,虽然肯定是有解决了这个问题的控件,但是还是决定自己写了,毕竟找到控件后还需要测试,而现在的项目时间不充分啊(另外欢迎指教如何快速的找到自己需要的控件,有时候在Github上面搜索,都不知道具体该用什么关键字),而且自己写,也是一种锻炼。"); 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.content.Intent; 4 | import android.content.pm.ActivityInfo; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.view.View; 9 | import android.widget.AdapterView; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.ListView; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | 16 | import wang.raye.preioc.PreIOC; 17 | import wang.raye.preioc.annotation.BindById; 18 | import wang.raye.preioc.annotation.OnItemClick; 19 | 20 | public class MainActivity extends AppCompatActivity { 21 | 22 | @BindById(R.id.list) 23 | ListView list; 24 | 25 | private ArrayList mActivities = null; 26 | 27 | protected void onCreate(android.os.Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | PreIOC.binder(this); 31 | 32 | try { 33 | 34 | PackageInfo pi = getPackageManager().getPackageInfo("wang.raye.myview", 35 | PackageManager.GET_ACTIVITIES); 36 | // 获取所有Activity信息 37 | mActivities = new ArrayList( 38 | Arrays.asList(pi.activities)); 39 | //获取本类的名字 40 | String ourName = getClass().getName(); 41 | //获取Activity的名字 42 | ArrayList names = new ArrayList(); 43 | 44 | for(int i = mActivities.size() - 1; i > -1;i--){ 45 | if(mActivities.get(i).name.equals(ourName)){ 46 | mActivities.remove(i); 47 | }else{ 48 | if(mActivities.get(i).nonLocalizedLabel != null){ 49 | names.add(0, mActivities.get(i).nonLocalizedLabel.toString()); 50 | }else{ 51 | mActivities.remove(i); 52 | } 53 | } 54 | } 55 | 56 | ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1); 57 | adapter.addAll(names); 58 | 59 | list.setAdapter(adapter); 60 | 61 | 62 | } catch (PackageManager.NameNotFoundException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | @OnItemClick({R.id.list}) 68 | public void onItemClick(AdapterView parent, View view, 69 | int position, long id) { 70 | Intent intent = new Intent(); 71 | intent.setClassName(this, mActivities.get(position).name); 72 | startActivity(intent); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/MoreTextViewActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import wang.raye.library.widge.MoreTextView; 7 | import wang.raye.preioc.PreIOC; 8 | import wang.raye.preioc.annotation.BindById; 9 | 10 | public class MoreTextViewActivity extends AppCompatActivity { 11 | 12 | @BindById(R.id.mtv_text) 13 | MoreTextView mtvText; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_more); 19 | PreIOC.binder(this); 20 | mtvText.setText("因为公司项目需要全文收起的功能,一共有2种UI,所以需要写2个全文收起的控件,之前也是用过一个全文收起的控件,但是因为设计原因,在ListView刷新的时候会闪烁,我估计原因是因为控件本身的设计是需要先让TextView绘制完成,然后获取TextView一共有多少行,再判断是否需要全文收起按钮,如果需要,则吧TextView压缩回最大行数,添加全文按钮,这样就会造成ListView的Item先高后低,所以会发生闪烁,后面我也在网上找了几个,发现和之前的设计都差不多,虽然肯定是有解决了这个问题的控件,但是还是决定自己写了,毕竟找到控件后还需要测试,而现在的项目时间不充分啊(另外欢迎指教如何快速的找到自己需要的控件,有时候在Github上面搜索,都不知道具体该用什么关键字),而且自己写,也是一种锻炼。"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/NonRecursiveActivity.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | import android.widget.LinearLayout; 10 | 11 | import java.util.ArrayList; 12 | 13 | import wang.raye.preioc.PreIOC; 14 | import wang.raye.preioc.annotation.BindById; 15 | 16 | /** 17 | * 不用递归遍历所有view节点的activity 18 | * Create by Raye on 2016年8月25日10:56:33 19 | */ 20 | public class NonRecursiveActivity extends AppCompatActivity { 21 | 22 | @BindById(R.id.rl_main) 23 | LinearLayout rlMain; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_non_recursive); 29 | PreIOC.binder(this); 30 | 31 | ArrayList groups = new ArrayList<>(); 32 | groups.add(rlMain); 33 | while (groups.size() > 0){ 34 | View view = groups.get(0); 35 | groups.remove(0); 36 | for(int j = 0;j < ((ViewGroup) view).getChildCount();j++){ 37 | View temp = ((ViewGroup) view).getChildAt(j); 38 | if(temp instanceof ViewGroup){ 39 | groups.add(temp); 40 | }else if(temp instanceof Button){ 41 | temp.setBackgroundColor(Color.RED); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/wang/raye/myview/services/BinderService.java: -------------------------------------------------------------------------------- 1 | package wang.raye.myview.services; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.Binder; 6 | import android.os.Handler; 7 | import android.os.IBinder; 8 | 9 | public class BinderService extends Service { 10 | private Thread thread = new Thread(new Runnable() { 11 | @Override 12 | public void run() { 13 | try { 14 | while (isContinue) { 15 | if (handler != null) { 16 | handler.sendEmptyMessage(1); 17 | } 18 | Thread.sleep(3000); 19 | } 20 | } catch (InterruptedException e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | }); 25 | 26 | private boolean isContinue = true; 27 | private Handler handler ; 28 | 29 | public BinderService() { 30 | } 31 | 32 | @Override 33 | public IBinder onBind(Intent intent) { 34 | thread.start(); 35 | return new MyBinder(); 36 | } 37 | 38 | @Override 39 | public boolean onUnbind(Intent intent) { 40 | isContinue = false; 41 | return super.onUnbind(intent); 42 | } 43 | 44 | public class MyBinder extends Binder { 45 | 46 | public void stop() { 47 | isContinue = false; 48 | } 49 | 50 | public void setHandler(Handler handler) { 51 | BinderService.this.handler = handler; 52 | } 53 | 54 | public BinderService getServices() { 55 | return BinderService.this; 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/widget/AbstractPathAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package widget; 17 | 18 | import android.content.res.Resources; 19 | import android.content.res.TypedArray; 20 | import android.graphics.Path; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | import java.util.Random; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | 27 | import wang.raye.myview.R; 28 | 29 | 30 | public abstract class AbstractPathAnimator { 31 | private final Random mRandom; 32 | protected final Config mConfig; 33 | 34 | 35 | public AbstractPathAnimator(Config config) { 36 | mConfig = config; 37 | mRandom = new Random(); 38 | } 39 | 40 | public float randomRotation() { 41 | return mRandom.nextFloat() * 28.6F - 14.3F; 42 | } 43 | 44 | public Path createPath(AtomicInteger counter, View view, int factor) { 45 | Random r = mRandom; 46 | int x = r.nextInt(mConfig.xRand); 47 | int x2 = r.nextInt(mConfig.xRand); 48 | int y = view.getHeight() - mConfig.initY; 49 | int y2 = counter.intValue() * 15 + mConfig.animLength * factor + r.nextInt(mConfig.animLengthRand); 50 | factor = y2 / mConfig.bezierFactor; 51 | x = mConfig.xPointFactor + x; 52 | x2 = mConfig.xPointFactor + x2; 53 | int y3 = y - y2; 54 | y2 = y - y2 / 2; 55 | Path p = new Path(); 56 | p.moveTo(mConfig.initX, y); 57 | p.cubicTo(mConfig.initX, y - factor, x, y2 + factor, x, y2); 58 | p.moveTo(x, y2); 59 | p.cubicTo(x, y2 - factor, x2, y3 + factor, x2, y3); 60 | return p; 61 | } 62 | 63 | public abstract void start(View child, ViewGroup parent); 64 | 65 | public static class Config { 66 | public int initX; 67 | public int initY; 68 | public int xRand; 69 | public int animLengthRand; 70 | public int bezierFactor; 71 | public int xPointFactor; 72 | public int animLength; 73 | public int heartWidth; 74 | public int heartHeight; 75 | public int animDuration; 76 | 77 | static Config fromTypeArray(TypedArray typedArray) { 78 | Config config = new Config(); 79 | Resources res = typedArray.getResources(); 80 | config.initX = (int) typedArray.getDimension(R.styleable.HeartLayout_initX, 81 | res.getDimensionPixelOffset(R.dimen.heart_anim_init_x)); 82 | config.initY = (int) typedArray.getDimension(R.styleable.HeartLayout_initY, 83 | res.getDimensionPixelOffset(R.dimen.heart_anim_init_y)); 84 | config.xRand = (int) typedArray.getDimension(R.styleable.HeartLayout_xRand, 85 | res.getDimensionPixelOffset(R.dimen.heart_anim_bezier_x_rand)); 86 | config.animLength = (int) typedArray.getDimension(R.styleable.HeartLayout_animLength, 87 | res.getDimensionPixelOffset(R.dimen.heart_anim_length)); 88 | config.animLengthRand = (int) typedArray.getDimension(R.styleable.HeartLayout_animLengthRand, 89 | res.getDimensionPixelOffset(R.dimen.heart_anim_length_rand)); 90 | config.bezierFactor = typedArray.getInteger(R.styleable.HeartLayout_bezierFactor, 91 | res.getInteger(R.integer.heart_anim_bezier_factor)); 92 | config.xPointFactor = (int) typedArray.getDimension(R.styleable.HeartLayout_xPointFactor, 93 | res.getDimensionPixelOffset(R.dimen.heart_anim_x_point_factor)); 94 | config.heartWidth = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_width, 95 | res.getDimensionPixelOffset(R.dimen.heart_size_width)); 96 | config.heartHeight = (int) typedArray.getDimension(R.styleable.HeartLayout_heart_height, 97 | res.getDimensionPixelOffset(R.dimen.heart_size_height)); 98 | config.animDuration = typedArray.getInteger(R.styleable.HeartLayout_anim_duration, 99 | res.getInteger(R.integer.anim_duration)); 100 | return config; 101 | } 102 | 103 | 104 | } 105 | 106 | 107 | } 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/java/widget/HeartLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package widget; 17 | 18 | import android.content.Context; 19 | import android.content.res.TypedArray; 20 | import android.util.AttributeSet; 21 | import android.widget.ImageView; 22 | import android.widget.RelativeLayout; 23 | 24 | import wang.raye.myview.R; 25 | 26 | 27 | public class HeartLayout extends RelativeLayout { 28 | 29 | private AbstractPathAnimator mAnimator; 30 | 31 | public HeartLayout(Context context) { 32 | super(context); 33 | init(null, 0); 34 | } 35 | 36 | public HeartLayout(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | init(attrs, 0); 39 | } 40 | 41 | public HeartLayout(Context context, AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | init(attrs, defStyleAttr); 44 | } 45 | 46 | private void init(AttributeSet attrs, int defStyleAttr) { 47 | 48 | final TypedArray a = getContext().obtainStyledAttributes( 49 | attrs, R.styleable.HeartLayout, defStyleAttr, 0); 50 | 51 | mAnimator = new PathAnimator(AbstractPathAnimator.Config.fromTypeArray(a)); 52 | 53 | a.recycle(); 54 | } 55 | 56 | public AbstractPathAnimator getAnimator() { 57 | return mAnimator; 58 | } 59 | 60 | public void setAnimator(AbstractPathAnimator animator) { 61 | clearAnimation(); 62 | mAnimator = animator; 63 | } 64 | 65 | public void clearAnimation() { 66 | for (int i = 0; i < getChildCount(); i++) { 67 | getChildAt(i).clearAnimation(); 68 | } 69 | removeAllViews(); 70 | } 71 | 72 | public void addHeart(int color) { 73 | HeartView heartView = new HeartView(getContext()); 74 | heartView.setColor(color); 75 | mAnimator.start(heartView, this); 76 | } 77 | 78 | 79 | public void addHeart(int color, int heartResId, int heartBorderResId) { 80 | HeartView heartView = new HeartView(getContext()); 81 | heartView.setColorAndDrawables(color, heartResId, heartBorderResId); 82 | mAnimator.start(heartView, this); 83 | } 84 | 85 | public void addImage(int img){ 86 | ImageView imageView = new ImageView(getContext()); 87 | imageView.setImageResource(img); 88 | mAnimator.start(imageView,this); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/widget/HeartView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package widget; 17 | 18 | import android.content.Context; 19 | import android.graphics.Bitmap; 20 | import android.graphics.BitmapFactory; 21 | import android.graphics.Canvas; 22 | import android.graphics.Paint; 23 | import android.graphics.PorterDuff; 24 | import android.graphics.PorterDuffColorFilter; 25 | import android.graphics.drawable.BitmapDrawable; 26 | import android.util.AttributeSet; 27 | import android.widget.ImageView; 28 | 29 | import wang.raye.myview.R; 30 | 31 | 32 | public class HeartView extends ImageView { 33 | 34 | private static final Paint sPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 35 | private int mHeartResId = R.mipmap.heart; 36 | private int mHeartBorderResId = R.mipmap.heart_border; 37 | private static Bitmap sHeart; 38 | private static Bitmap sHeartBorder; 39 | private static final Canvas sCanvas = new Canvas(); 40 | 41 | public HeartView(Context context, AttributeSet attrs) { 42 | super(context, attrs); 43 | } 44 | 45 | public HeartView(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | } 48 | 49 | public HeartView(Context context) { 50 | super(context); 51 | } 52 | 53 | public void setColor(int color) { 54 | Bitmap heart = createHeart(color); 55 | setImageDrawable(new BitmapDrawable(getResources(), heart)); 56 | } 57 | 58 | public void setColorAndDrawables(int color, int heartResId, int heartBorderResId) { 59 | if (heartResId != mHeartResId) { 60 | sHeart = null; 61 | } 62 | if (heartBorderResId != mHeartBorderResId) { 63 | sHeartBorder = null; 64 | } 65 | mHeartResId = heartResId; 66 | mHeartBorderResId = heartBorderResId; 67 | setColor(color); 68 | } 69 | 70 | public Bitmap createHeart(int color) { 71 | if (sHeart == null) { 72 | sHeart = BitmapFactory.decodeResource(getResources(), mHeartResId); 73 | } 74 | if (sHeartBorder == null) { 75 | sHeartBorder = BitmapFactory.decodeResource(getResources(), mHeartBorderResId); 76 | } 77 | Bitmap heart = sHeart; 78 | Bitmap heartBorder = sHeartBorder; 79 | Bitmap bm = createBitmapSafely(heartBorder.getWidth(), heartBorder.getHeight()); 80 | if (bm == null) { 81 | return null; 82 | } 83 | Canvas canvas = sCanvas; 84 | canvas.setBitmap(bm); 85 | Paint p = sPaint; 86 | canvas.drawBitmap(heartBorder, 0, 0, p); 87 | p.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); 88 | float dx = (heartBorder.getWidth() - heart.getWidth()) / 2f; 89 | float dy = (heartBorder.getHeight() - heart.getHeight()) / 2f; 90 | canvas.drawBitmap(heart, dx, dy, p); 91 | p.setColorFilter(null); 92 | canvas.setBitmap(null); 93 | return bm; 94 | } 95 | 96 | private static Bitmap createBitmapSafely(int width, int height) { 97 | try { 98 | return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 99 | } catch (OutOfMemoryError error) { 100 | error.printStackTrace(); 101 | } 102 | return null; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/java/widget/PathAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package widget; 17 | 18 | import android.graphics.Matrix; 19 | import android.graphics.Path; 20 | import android.graphics.PathMeasure; 21 | import android.os.Handler; 22 | import android.os.Looper; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | import android.view.animation.Animation; 26 | import android.view.animation.LinearInterpolator; 27 | import android.view.animation.Transformation; 28 | 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | 31 | public class PathAnimator extends AbstractPathAnimator { 32 | private final AtomicInteger mCounter = new AtomicInteger(0); 33 | private Handler mHandler; 34 | 35 | public PathAnimator(Config config) { 36 | super(config); 37 | mHandler = new Handler(Looper.getMainLooper()); 38 | } 39 | 40 | @Override 41 | public void start(final View child, final ViewGroup parent) { 42 | parent.addView(child, new ViewGroup.LayoutParams(mConfig.heartWidth, mConfig.heartHeight)); 43 | FloatAnimation anim = new FloatAnimation(createPath(mCounter, parent, 2), randomRotation(), parent, child); 44 | anim.setDuration(mConfig.animDuration); 45 | anim.setInterpolator(new LinearInterpolator()); 46 | anim.setAnimationListener(new Animation.AnimationListener() { 47 | @Override 48 | public void onAnimationEnd(Animation animation) { 49 | mHandler.post(new Runnable() { 50 | @Override 51 | public void run() { 52 | parent.removeView(child); 53 | } 54 | }); 55 | mCounter.decrementAndGet(); 56 | } 57 | 58 | @Override 59 | public void onAnimationRepeat(Animation animation) { 60 | 61 | } 62 | 63 | @Override 64 | public void onAnimationStart(Animation animation) { 65 | mCounter.incrementAndGet(); 66 | } 67 | }); 68 | anim.setInterpolator(new LinearInterpolator()); 69 | child.startAnimation(anim); 70 | } 71 | 72 | static class FloatAnimation extends Animation { 73 | private PathMeasure mPm; 74 | private View mView; 75 | private float mDistance; 76 | private float mRotation; 77 | 78 | public FloatAnimation(Path path, float rotation, View parent, View child) { 79 | mPm = new PathMeasure(path, false); 80 | mDistance = mPm.getLength(); 81 | mView = child; 82 | mRotation = rotation; 83 | parent.setLayerType(View.LAYER_TYPE_HARDWARE, null); 84 | } 85 | 86 | @Override 87 | protected void applyTransformation(float factor, Transformation transformation) { 88 | Matrix matrix = transformation.getMatrix(); 89 | mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG); 90 | mView.setRotation(mRotation * factor); 91 | float scale = 1F; 92 | if (3000.0F * factor < 200.0F) { 93 | scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D); 94 | } else if (3000.0F * factor < 300.0F) { 95 | scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D); 96 | } 97 | mView.setScaleX(scale); 98 | mView.setScaleY(scale); 99 | transformation.setAlpha(1.0F - factor); 100 | } 101 | } 102 | 103 | private static float scale(double a, double b, double c, double d, double e) { 104 | return (float) ((a - b) / (c - b) * (e - d) + d); 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /app/src/main/res/anim/flower.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 16 | 22 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 13 | 14 | 21 |