Repository: keyboard3/HencoderKeyboard3 Branch: master Commit: 2de932b174fd Files: 52 Total size: 106.2 KB Directory structure: gitextract_oi1ooax2/ ├── .gitignore ├── README.md ├── app/ │ ├── .gitignore │ ├── build/ │ │ └── outputs/ │ │ └── apk/ │ │ └── debug/ │ │ └── app-debug.apk │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── keyboard3/ │ │ └── hencoderProduct/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── keyboard3/ │ │ │ └── hencoderProduct/ │ │ │ ├── MainActivity.java │ │ │ ├── PageFragment.java │ │ │ ├── Utils.java │ │ │ ├── filpboard/ │ │ │ │ ├── FlipboardLayout.java │ │ │ │ └── FlipboardView.java │ │ │ ├── like/ │ │ │ │ ├── LikeImageView.java │ │ │ │ ├── LikeLayout.java │ │ │ │ ├── LikeNumView.java │ │ │ │ └── LikeView.java │ │ │ ├── movement/ │ │ │ │ ├── MoveLayout.java │ │ │ │ └── MoveView.java │ │ │ └── ruler/ │ │ │ └── RulerLayout.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── filpboard_layout.xml │ │ │ ├── fragment_page.xml │ │ │ ├── like_layout.xml │ │ │ ├── move_layout.xml │ │ │ └── ruler_layout.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── com/ │ └── keyboard3/ │ └── hencoderProduct/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── ruler/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── keyboard3/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── keyboard3/ │ │ │ ├── BooheeRuler.java │ │ │ ├── InnerRuler.java │ │ │ ├── RulerCallback.java │ │ │ └── RulerNumberLayout.java │ │ └── res/ │ │ ├── drawable/ │ │ │ └── cursor_shape.xml │ │ ├── layout/ │ │ │ └── layout_kg_number.xml │ │ └── values/ │ │ ├── attr.xml │ │ ├── colors.xml │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── keyboard3/ │ └── ExampleUnitTest.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .externalNativeBuild /.idea/ ================================================ FILE: README.md ================================================ # HencoderKeyboard3 [HenCoder「仿写酷界面」活动——征稿](http://hencoder.com/activity-mock-1)
[HenCoder「仿写酷界面」活动——获奖作品点评](http://hencoder.com/activity-mock-2/)
虽然4个作品均未命中奖项,但本人还是会持续优化....,敬请关注!!! - 写作背景
在这个作品之前都是比较零散的搞一个比较好的效果仿写,比如我之前仿写 [QQ 健康效果](https://github.com/keyboard3/SelfView)。 hencoder 这个系列很完整,比之其他博主写的形式更加通俗易懂,我觉得这些都是次要的,都是知识获取渠道,最重要的是将知识落地。这场比赛我觉得很好,投稿、点评很能够调用一个开发者的兴趣。说实话在邀请投稿文章出来之后,自定义 View 经验不是很丰富的我居然在十几个小时就连续将4个作品全部仿写出来投稿了,可以说是调动了我很高的兴趣和潜力。当然作品还是有很多欠缺的地方,我会不断的进行完善。 ## 下载 [demo.apk](app/build/outputs/apk/debug/app-debug.apk) # 即刻点赞 - 写在前面 获奖作品[ThumbUpSample](https://github.com/arvinljw/ThumbUpSample)
``` 在揭晓优胜者之后我对比了实现原理,基本原理就是左边炫丽的效果和右边文字上下滚动分离。我在左边的效果实现上忘记了一个光圈的缩放,当时以为是鼠标自己的效果....,比较遗憾! ``` - 实现原理 ``` 实现原理:隔离并复用图片动画和文字动画 LikeView自定义的LinearLayout默认组合点赞文字动画效果 LikeImageView 点赞图片动画 点赞效果 灰色的点赞图标变小至0.9倍 转变成红色的图标并且半透明 红色图标逐渐增长至正常的1.1倍 / 透明度在增长至正常时逐渐变成实体 最后变成正常大小的红色图标 点赞伴随动画光圈 0.6倍点赞图标大小的光圈、半透明 0-50%动画完成度时半透明变成实体 50%-100%动画完成度时实体又逐渐变成透明 光圈半径逐渐增大至1.1倍 取消点赞效果 红色图标变小至0.9倍且变成半透明 动画完成到一半时变成灰色的正常大小 闪光动画 点赞时 闪光图标在点赞图标顶部的某个位置,先由小到大直至正常大小 LikeNumView 点赞和取消赞动作导致的文字变化 转变成 原数字->新数字。点赞和取消赞时改动新数字的值(+1/-1)。将两个数字动转为字符串数组,从高位开始循环 如果数字相同就直接画数字,如果数字不同就开始绘制两个数字位移同时设置对应的透明渐变` ``` - 对比获奖作品 ``` 获奖作品之后并没有多大改动 点赞散开点并没有做处理 我主要是在我原来的基础上将基本特效都实现了,包括点赞散开的效果 ``` - 截图
# 薄荷健康尺 - 写在前面 获奖作品[BooheeRuler](https://github.com/totond/BooheeRuler)
``` 在揭晓获奖作品之后,看了当时的实现方式原理一致,中央高亮刻度覆盖尺子之上,尺子刻度值全部绘制出来,然后滑动内容。因为当时滑动交互算是hencoder的超纲内容,因而漏写了这一块很遗憾! ``` - 实现原理 ``` 分析: 此控件分两块,下方尺子和上方显示的中央刻度值。下方尺子中刻度内容可以滑动而在其上的中央高亮刻度固定。由上分析:将此控件分成两个view,rulerView和rulerNumerView rulerView:刻度要可以滑动即刻度是一个独立的View,上方固定的中央刻度则是ViewGroup在刻度子View绘制完成之后在上面覆盖绘制一个高亮的刻度线 rulerNumberView:实时监听rulerView的刻度滚动经过中央高亮刻度线时的值显示 ``` - 对比获奖作品 ``` 获奖作品在众星捧月中不断进步,截止0.1.3版本 功能上:实现了尺子的左上右下的四种显示方向 性能上:每次重绘只绘制当前显示部分刻度 我觉得它已经做的很完美了,但是我觉得还是有些补足之处的。 1.实现四种显示方向结构太臃肿、完全可以直接在InnerRuler中绘制刻度和文字等处做方向处理调整绘制坐标 2.监听刻度值的View可以做的更加解耦一些,rulerView和rulerNumberView可以做到n:n 我这里在他的作品之上做了上面我所说的改造,因为时间关系我阉割了他边界阴影效果 ``` - 截图 ## 小米运动 获奖作品[MISportsConnectWidget](https://github.com/sickworm/MISportsConnectWidget) - 写在前面 ``` 这个作品难点在于喷射的效果,因为经验较少直接采用了笨办法,稍微处理了一下随机算法。 最后看到有人实现这个效果之后真心佩服,原作者点评也很专业 ``` - 实现原理 ``` 带粒子喷射的头部以及虚化线尾巴旋转效果 最终页面显示结果的外光晕旋转 ``` - 作品对比 ``` 相形见绌啊,只讲我的缺点吧 粒子喷射效果没有已随机点击代替... 最终结果的外圈旋转光晕没有... ``` - 截图 ## Fliboard 翻页效果 获奖作品[HenCoderPractice](https://github.com/sunnyxibei/HenCoderPractice) - 写在前面 ``` 这个是4个中我最先写出来的,稿子发出来之后,下班之前就发了出来。起初有点懵,当效果分拆成小块小块的时候,发现很容易实现,最后拼起来效果一致。 当这个作品没获奖的时候真的比较遗憾,可能是输给细节吧! ``` - 实现原理 ``` 将效果进行拆分成启动右侧的翻折、旋转翻折、结尾下方也翻折 难点在于如果实现旋转翻折,翻折部分和正常部分分开绘制。 带着翻折部分旋转:裁剪出翻折部分的canvas,将这部分canvs依照整体做旋转,然后将图片绘制在上面,最后反向旋转回来然后将这部分与正常部分拼接就完成了整个效果 ``` - 作品对比 ``` 差异不大,获奖者比较鸡贼,图片采用他们最新的图标.... ``` - 截图 ## 关于我 简书 [keyboard3](http://www.jianshu.com/users/62329de8c8a6/latest_articles)
邮箱 keyboard3@icloud.com ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion '26.0.2' defaultConfig { applicationId "com.keyboard3.hencoderProduct" minSdkVersion 18 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } apply plugin: 'replugin-plugin-gradle' dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.qihoo360.replugin:replugin-plugin-lib:2.2.0' androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support:support-core-ui:25.3.1' compile 'com.android.support:design:25.3.1' testCompile 'junit:junit:4.12' compile project(':ruler') } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/rengwuxian/.android-sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/keyboard3/hencoderProduct/ExampleInstrumentedTest.java ================================================ package com.keyboard3.hencoderProduct; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.hencoder.hencoderpracticedraw5", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/MainActivity.java ================================================ package com.keyboard3.hencoderProduct; import android.os.Bundle; import android.os.Handler; import android.os.MessageQueue; import android.support.annotation.LayoutRes; import android.support.annotation.StringRes; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { TabLayout tabLayout; ViewPager pager; List pageModels = new ArrayList<>(); { pageModels.add(new PageModel(R.layout.like_layout, R.string.title_like)); pageModels.add(new PageModel(R.layout.ruler_layout, R.string.title_ruler)); pageModels.add(new PageModel(R.layout.move_layout, R.string.title_move)); pageModels.add(new PageModel(R.layout.filpboard_layout, R.string.title_flipboard)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pager = (ViewPager) findViewById(R.id.pager); pager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { @Override public Fragment getItem(int position) { PageModel pageModel = pageModels.get(position); return PageFragment.newInstance(pageModel.productLayoutRes); } @Override public int getCount() { return pageModels.size(); } @Override public CharSequence getPageTitle(int position) { return getString(pageModels.get(position).titleRes); } }); tabLayout = (TabLayout) findViewById(R.id.tabLayout); tabLayout.setupWithViewPager(pager); } @Override public boolean onCreateOptionsMenu(Menu menu) { return super.onCreateOptionsMenu(menu); } private class PageModel { @LayoutRes int productLayoutRes; @StringRes int titleRes; PageModel(@LayoutRes int sampleLayoutRes, @StringRes int titleRes) { this.productLayoutRes = sampleLayoutRes; this.titleRes = titleRes; } } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/PageFragment.java ================================================ package com.keyboard3.hencoderProduct; import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; public class PageFragment extends Fragment { @LayoutRes int productLayoutRes; public static PageFragment newInstance(@LayoutRes int productLayoutRes) { PageFragment fragment = new PageFragment(); Bundle args = new Bundle(); args.putInt("productLayoutRes", productLayoutRes); fragment.setArguments(args); return fragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_page, container, false); ViewStub sampleStub = (ViewStub) view.findViewById(R.id.sampleStub); sampleStub.setLayoutResource(productLayoutRes); sampleStub.inflate(); return view; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); if (args != null) { productLayoutRes = args.getInt("productLayoutRes"); } } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/Utils.java ================================================ package com.keyboard3.hencoderProduct; import android.content.res.Resources; import android.util.DisplayMetrics; public class Utils { public static float dpToPixel(float dp) { DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); return dp * metrics.density; } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/filpboard/FlipboardLayout.java ================================================ package com.keyboard3.hencoderProduct.filpboard; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.widget.Button; import android.widget.RelativeLayout; import com.keyboard3.hencoderProduct.R; public class FlipboardLayout extends RelativeLayout { FlipboardView view; Button animateBt; public FlipboardLayout(Context context) { super(context); } public FlipboardLayout(Context context, AttributeSet attrs) { super(context, attrs); } public FlipboardLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public void draw(Canvas canvas) { super.draw(canvas); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); view = (FlipboardView) findViewById(R.id.objectAnimatorView); animateBt = (Button) findViewById(R.id.animateBt); animateBt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { view.clearCanvas(); ObjectAnimator animator1 = ObjectAnimator.ofInt(view, "turnoverDegreeFirst", 0, 30); animator1.setDuration(1000); animator1.setInterpolator(new LinearInterpolator()); ObjectAnimator animator2 = ObjectAnimator.ofInt(view, "degree", 0, 270); animator2.setDuration(2000); animator2.setInterpolator(new AccelerateDecelerateInterpolator()); ObjectAnimator animator3 = ObjectAnimator.ofInt(view, "turnoverDegreeLast", 0, 30); animator3.setDuration(1000); animator3.setInterpolator(new LinearInterpolator()); AnimatorSet animatorSet = new AnimatorSet(); // 两个动画依次执行 animatorSet.playSequentially(animator1, animator2, animator3); animatorSet.start(); } }); } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/filpboard/FlipboardView.java ================================================ package com.keyboard3.hencoderProduct.filpboard; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Camera; import android.graphics.Canvas; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.keyboard3.hencoderProduct.R; /** * @author keyboard3 */ public class FlipboardView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Camera mCamera = new Camera(); private Bitmap mBitmap; private int turnoverDegreeFirst; private int turnoverDegreeLast; private int degree; public FlipboardView(Context context) { super(context); } public FlipboardView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public FlipboardView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } { mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.filpboard); } public void clearCanvas() { degree = 0; turnoverDegreeFirst = 0; turnoverDegreeLast = 0; invalidate(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int bitmapWidth = mBitmap.getWidth(); int bitmapHeight = mBitmap.getHeight(); int centerX = getWidth() / 2; int centerY = getHeight() / 2; int x = centerX - bitmapWidth / 2; int y = centerY - bitmapHeight / 2; //绘制上半部分 canvas.save(); canvas.rotate(-degree, centerX, centerY); canvas.clipRect(0, 0, centerX, getHeight()); canvas.rotate(degree, centerX, centerY); //翻折画布 mCamera.save(); mCamera.rotateX(-turnoverDegreeLast); canvas.translate(centerX, centerY); mCamera.applyToCanvas(canvas); canvas.translate(-centerX, -centerY); mCamera.restore(); canvas.drawBitmap(mBitmap, x, y, mPaint); canvas.restore(); //绘制右边部分 canvas.save(); canvas.rotate(-degree, centerX, centerY); canvas.clipRect(centerX, 0, getWidth(), getHeight()); //翻折画布 mCamera.save(); mCamera.rotateY(-turnoverDegreeFirst); canvas.translate(centerX, centerY); mCamera.applyToCanvas(canvas); canvas.translate(-centerX, -centerY); mCamera.restore(); canvas.rotate(degree, centerX, centerY); //向画布绘制内容 canvas.drawBitmap(mBitmap, x, y, mPaint); canvas.restore(); } @SuppressWarnings("unused") public void setDegree(int degree) { this.degree = degree; invalidate(); } @SuppressWarnings("unused") public void setTurnoverDegreeLast(int turnoverDegreeLast) { this.turnoverDegreeLast = turnoverDegreeLast; invalidate(); } @SuppressWarnings("unused") public void setTurnoverDegreeFirst(int turnoverDegreeFirst) { this.turnoverDegreeFirst = turnoverDegreeFirst; invalidate(); } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/like/LikeImageView.java ================================================ package com.keyboard3.hencoderProduct.like; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.IdRes; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; /** * @author keyboard3 * @date 2017/10/30 */ public class LikeImageView extends View { private Paint mImagePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private boolean liked = false; private float animProgress; private Bitmap mUnlikeBitmap; private Bitmap mLikedBitmap; private Bitmap mShiningBitmap; private float leftPadding; private float middlePadding; private float startX; private int centerY; public LikeImageView(Context context) { super(context); } public LikeImageView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public LikeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } { mImagePaint.setColor(Color.parseColor("#c3c4c3")); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { startX = mLikedBitmap.getWidth() * 0.1f + leftPadding; int width = (int) (mLikedBitmap.getWidth() * 1.1); int height = mLikedBitmap.getHeight() + mShiningBitmap.getHeight(); setMeasuredDimension((int) (width + leftPadding + middlePadding), height); } public void setLike(boolean isLike) { liked = isLike; if (!liked) { animProgress = 0; } invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); centerY = getHeight() / 2; //绘制放大圈 drawCircle(canvas, (int) (startX + mLikedBitmap.getWidth() / 2), centerY); //绘制点赞图片 int likeTop = centerY - mUnlikeBitmap.getHeight() / 2; drawLike(canvas, likeTop, (int) startX); //绘制闪光 drawShining(canvas, likeTop, (int) startX); } private void drawCircle(Canvas canvas, int centerX, int centerY) { float radius = 0; int alpha = 0; if (liked&&animProgress > 0) { //透明变实体 if (animProgress > 0 && animProgress <= 0.5) { alpha = (int) (255 * (0.5 + animProgress)); } else { //实体变透明 alpha = (int) (255 * (1 - (animProgress - 0.5) * 2)); } radius = (float) (0.6 + animProgress * 0.7); } mImagePaint.setColor(Color.parseColor("#cc775c")); mImagePaint.setAlpha(alpha); mImagePaint.setStyle(Paint.Style.STROKE); mImagePaint.setStrokeWidth(3); canvas.drawCircle(centerX, centerY, radius * mLikedBitmap.getWidth() / 2, mImagePaint); mImagePaint.setColor(Color.parseColor("#c3c4c3")); mImagePaint.setStyle(Paint.Style.FILL); } private void drawLike(Canvas canvas, int likeTop, int likeLeft) { Bitmap bitmap = null; float scale = 0; if (liked) { //灰色一下变小 if (animProgress > 0 && animProgress <= 0.1) { bitmap = mUnlikeBitmap; scale = -0.01f; } //红色小且半透明 变正常过程就变成了实体 if (animProgress > 0.1 && animProgress <= 0.5) { mImagePaint.setAlpha((int) (255 * (0.5 + animProgress))); } else { mImagePaint.setAlpha(255); } //红色放大 if (animProgress > 0.1 && animProgress <= 0.9) { bitmap = mLikedBitmap; scale = (float) (-0.01f + animProgress * 0.1); } //一瞬间变正常 if (animProgress > 0.9 || animProgress == 0) { bitmap = mLikedBitmap; scale = 0; } } else { //红色缩小 变半透明 if (animProgress > 0 && animProgress <= 0.5) { bitmap = mLikedBitmap; mImagePaint.setAlpha((int) (255 * (0.5 + animProgress))); scale = (float) (-animProgress * 0.1); } //一半的时候变灰色 if (animProgress > 0.5 || animProgress == 0) { mImagePaint.setAlpha(255); bitmap = mUnlikeBitmap; scale = 0; } } canvas.save(); canvas.translate((float) (likeLeft - bitmap.getWidth() * 0.05), (float) (likeTop - bitmap.getHeight() * 0.05)); canvas.scale(1 + scale, 1 + scale); canvas.drawBitmap(bitmap, 0, 0, mImagePaint); canvas.restore(); } private void drawShining(Canvas canvas, int likeTop, int likeLeft) { if (liked&&animProgress > 0) { float scale = animProgress; int shiningTop = (int) (likeTop - scale * mShiningBitmap.getHeight() / 2); int shiningLeft = (int) (likeLeft + (mLikedBitmap.getWidth() - scale * mShiningBitmap.getWidth()) / 2); canvas.save(); canvas.translate(shiningLeft, shiningTop); canvas.scale(scale, scale); canvas.drawBitmap(mShiningBitmap, 0, 0, mImagePaint); canvas.restore(); } } @SuppressWarnings("unused") public void setAnimProgress(float animProgress) { this.animProgress = animProgress; invalidate(); } public void setMiddlePadding(float middlePadding) { this.middlePadding = middlePadding; } public void setLeftPadding(float leftPadding) { this.leftPadding = leftPadding; } public void setUnlikeSrc(@IdRes int unlikeSrc) { mUnlikeBitmap = BitmapFactory.decodeResource(getResources(), unlikeSrc); } public void setLikedSrc(@IdRes int likeSrc) { mLikedBitmap = BitmapFactory.decodeResource(getResources(), likeSrc); } public void setShiningdSrc(@IdRes int shiningSrc) { mShiningBitmap = BitmapFactory.decodeResource(getResources(), shiningSrc); } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/like/LikeLayout.java ================================================ package com.keyboard3.hencoderProduct.like; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RelativeLayout; import com.keyboard3.hencoderProduct.R; public class LikeLayout extends RelativeLayout { LikeView view1, view2, view3; Button animateBt; public LikeLayout(Context context) { super(context); } public LikeLayout(Context context, AttributeSet attrs) { super(context, attrs); } public LikeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); view1 = (LikeView) findViewById(R.id.objectAnimatorView1); view2 = (LikeView) findViewById(R.id.objectAnimatorView2); view3 = (LikeView) findViewById(R.id.objectAnimatorView3); animateBt = (Button) findViewById(R.id.animateBt); animateBt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { view1.performClick(); view2.performClick(); view3.performClick(); } }); } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/like/LikeNumView.java ================================================ package com.keyboard3.hencoderProduct.like; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.keyboard3.hencoderProduct.Utils; /** * @author keyboard3 */ public class LikeNumView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private int mCurNum = 0; private int mNewNum = 0; private int translationY; private boolean liked = false; private int mMoveY; private int mTextSize; private int centerY; private float rightPadding; public LikeNumView(Context context) { super(context); } public LikeNumView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public LikeNumView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } { mTextSize = (int) Utils.dpToPixel(12); mPaint.setTextSize(mTextSize); mPaint.setColor(Color.parseColor("#c3c4c3")); } protected void setNum(int num) { mCurNum = mNewNum = num; invalidate(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //保证长度足够 String curNum = (mCurNum + 1) * 10 + ""; Rect rect = new Rect(); mPaint.getTextBounds(curNum, 0, curNum.length(), rect); float width = rect.width() + rightPadding; int height = mTextSize * 3; int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); width = resolveSize((int) width, widthSpecSize); setMeasuredDimension((int) width, height); mMoveY = height / 2; } protected void changeLike(boolean isLike) { if (isLike) { if (mCurNum != 0) { mNewNum = mCurNum - 1; } } else { mNewNum = mCurNum + 1; } liked = !isLike; } protected void init() { mCurNum = mNewNum; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); centerY = getHeight() / 2; int leftX = 0; Rect rect = new Rect(); mPaint.getTextBounds("0", 0, 1, rect); drawAnimNum(canvas, leftX, centerY - (rect.top + rect.bottom) / 2, mCurNum, mNewNum); } private void drawAnimNum(Canvas canvas, int leftX, int baseTxtY, int curNum, int newNum) { String curNumStr = (curNum + "").toString(); String newNumStr = (newNum + "").toString(); int len = Math.max(curNumStr.length(), newNumStr.length()); float charLen = mPaint.measureText("0"); int sumLeft = leftX; String curCharTxt, newCharTxt; for (int i = 0; i < len; i++) { if (i > curNumStr.length() - 1) { curCharTxt = ""; } else { curCharTxt = curNumStr.substring(i, i + 1); } if (i > newNumStr.length() - 1) { newCharTxt = ""; } else { newCharTxt = newNumStr.substring(i, i + 1); } optDrawNum(canvas, sumLeft, baseTxtY, curCharTxt, newCharTxt, newNum > curNum); sumLeft += charLen; } } private void optDrawNum(Canvas canvas, int leftX, int baseTxtY, String curNum, String newNum, boolean upOrDown) { if (curNum.equals(newNum)) { mPaint.setAlpha(255); canvas.drawText(curNum, leftX, baseTxtY, mPaint); return; } int alpha = (int) ((1 - 1.0 * translationY / mMoveY) * 255); int curBaseY = baseTxtY; int newBaseY, transY; if (upOrDown) { transY = -translationY; newBaseY = baseTxtY + mMoveY; } else {//down -1 transY = translationY; newBaseY = baseTxtY - mMoveY; } mPaint.setAlpha(alpha); canvas.drawText(curNum, leftX, curBaseY + transY, mPaint); mPaint.setAlpha(255 - alpha); canvas.drawText(newNum, leftX, newBaseY + transY, mPaint); mPaint.setAlpha(255); } public void setLiked(boolean liked) { this.liked = liked; } @SuppressWarnings("unused") public void setTranslationY(int translationY) { this.translationY = translationY; invalidate(); } public void setRightPadding(float rightPadding) { this.rightPadding = rightPadding; } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/like/LikeView.java ================================================ package com.keyboard3.hencoderProduct.like; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import com.keyboard3.hencoderProduct.R; /** * @author keyboard3 * @date 2017/10/30 */ public class LikeView extends LinearLayout { private int mAnimTime = 500; private LikeNumView likeNumView; private LikeImageView likeImageView; private AnimatorSet animatorSet; private boolean isLike = false; public LikeView(Context context) { super(context); } public LikeView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LikeView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); final TypedArray custom = context.obtainStyledAttributes( attrs, R.styleable.LikeView, defStyleAttr, 0); int likeNum = custom.getInt(R.styleable.LikeView_likeNum, 0); boolean liked = custom.getBoolean(R.styleable.LikeView_liked, false); float leftPadding = custom.getDimension(R.styleable.LikeView_leftPadding, 0); float middlePadding = custom.getDimension(R.styleable.LikeView_middlePadding, 0); float rightPadding = custom.getDimension(R.styleable.LikeView_rightPadding, 0); int likeSrc = custom.getResourceId(R.styleable.LikeView_likeSrc, R.mipmap.ic_messages_like_selected); int unlikeSrc = custom.getResourceId(R.styleable.LikeView_unlikeSrc, R.mipmap.ic_messages_like_unselected); int shiningSrc = custom.getResourceId(R.styleable.LikeView_shiningSrc, R.mipmap.ic_messages_like_selected_shining); likeImageView.setShiningdSrc(shiningSrc); likeImageView.setLikedSrc(likeSrc); likeImageView.setUnlikeSrc(unlikeSrc); likeNumView.setRightPadding(rightPadding); likeImageView.setLeftPadding(leftPadding); likeImageView.setMiddlePadding(middlePadding); likeNumView.setNum(likeNum); setLike(liked); } { setOrientation(HORIZONTAL); likeImageView = new LikeImageView(getContext()); addView(likeImageView); likeNumView = new LikeNumView(getContext()); addView(likeNumView); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!animatorSet.isRunning()) { likeNumView.changeLike(isLike); isLike = !isLike; likeImageView.setLike(isLike); animatorSet.start(); } } }); } public void setNum(int num) { likeNumView.setNum(num); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); animatorSet.end(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int mMoveY = getMeasuredHeight() / 2; ObjectAnimator numAnimator = ObjectAnimator.ofInt(likeNumView, "translationY", 0, mMoveY); numAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); likeNumView.init(); } }); numAnimator.setDuration(mAnimTime); ObjectAnimator imageAnimator = ObjectAnimator.ofFloat(likeImageView, "animProgress", 0, 1); imageAnimator.setDuration(mAnimTime); animatorSet = new AnimatorSet(); animatorSet.playTogether(numAnimator, imageAnimator); setMeasuredDimension(likeImageView.getMeasuredWidth() + likeNumView.getMeasuredWidth(), getMeasuredHeight()); } public void setLike(boolean like) { isLike = like; likeNumView.setLiked(isLike); likeImageView.setLike(isLike); invalidate(); } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/movement/MoveLayout.java ================================================ package com.keyboard3.hencoderProduct.movement; import android.content.Context; import android.util.AttributeSet; import android.view.VelocityTracker; import android.view.View; import android.widget.Button; import android.widget.RelativeLayout; import android.widget.Scroller; import com.keyboard3.hencoderProduct.R; public class MoveLayout extends RelativeLayout { MoveView view; Button animateBt; public MoveLayout(Context context) { super(context); } public MoveLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MoveLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); view = (MoveView) findViewById(R.id.objectAnimatorView); animateBt = (Button) findViewById(R.id.animateBt); OnClickListener listener = new OnClickListener() { @Override public void onClick(View v) { view.startAnimal(); } }; animateBt.setOnClickListener(listener); view.setOnClickListener(listener); } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/movement/MoveView.java ================================================ package com.keyboard3.hencoderProduct.movement; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PathEffect; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.SweepGradient; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.keyboard3.hencoderProduct.R; import com.keyboard3.hencoderProduct.Utils; import java.util.Random; /** * @author keyboard3 */ public class MoveView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private AnimatorSet mAnimatorSet = new AnimatorSet(); private boolean mDrawInner = false; private boolean mDrawOut = false; private boolean mDrawLoading = true; private String mStepNum; private String mKmNum; private String mCalNum; private Bitmap mWatchBitmap; private Random mRandom; private int transparentWhite; private int degree = 0; private int maxMove; private int centerX; private int centerY; public MoveView(Context context) { super(context); } public MoveView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } { setWillNotDraw(false); mPaint.setTextAlign(Paint.Align.CENTER); transparentWhite = Color.parseColor("#00ffffff"); maxMove = (int) Utils.dpToPixel(50); mWatchBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_watch); mRandom = new Random(); mStepNum = "2274"; mKmNum = "1.5公里"; mCalNum = "34千卡"; } public void startAnimal() { mDrawInner = false; mDrawOut = false; mDrawLoading = true; AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator animator0 = ObjectAnimator.ofInt(MoveView.this, "degree", 0, 480); animator0.setDuration(3000); animator0.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { mDrawLoading = false; } }); ObjectAnimator animator1 = ObjectAnimator.ofFloat(this, "translationY", 0, -maxMove); animator1.setDuration(500); animator1.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mDrawOut = true; } }); ObjectAnimator animator2 = ObjectAnimator.ofFloat(this, "translationY", -maxMove, 0); animator2.setDuration(500); animator2.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator animation) { mDrawInner = true; } }); animatorSet.playSequentially(animator0, animator1, animator2); animatorSet.start(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAnimatorSet.end(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + 200);//延长内容 } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); centerX = getWidth() / 2; centerY = getHeight() / 2; //绘制步数 mPaint.setStyle(Paint.Style.FILL); mPaint.setTextSize(90); mPaint.setColor(Color.parseColor("#ffffff")); Rect stepNumRect = new Rect(); mPaint.getTextBounds(mStepNum, 0, mStepNum.length() - 1, stepNumRect); int stepNumBaseY = centerY - (stepNumRect.top + stepNumRect.bottom) / 2; canvas.drawText(mStepNum, centerX, stepNumBaseY, mPaint); //绘制km mPaint.setTextSize(24); mPaint.setColor(Color.parseColor("#b1d6f8")); Rect kmNumRect = new Rect(); mPaint.getTextBounds(mKmNum, 0, mKmNum.length() - 1, kmNumRect); int kmNumBaseX = centerX - 20 - (kmNumRect.left + kmNumRect.right) / 2; int kmNumBaseY = stepNumBaseY + 30 + kmNumRect.height(); canvas.drawText(mKmNum, kmNumBaseX, kmNumBaseY, mPaint); //绘制卡路里 Rect calNumRect = new Rect(); mPaint.getTextBounds(mCalNum, 0, mCalNum.length() - 1, calNumRect); int calNumBaseX = centerX + 20 + (calNumRect.left + calNumRect.right) / 2; int calNumBaseY = stepNumBaseY + 30 + kmNumRect.height(); canvas.drawText(mCalNum, calNumBaseX, calNumBaseY, mPaint); //绘制中间线 mPaint.setStrokeWidth(2); int centerLineTop = kmNumBaseY - kmNumRect.height(); int centerLineBottom = centerLineTop + kmNumRect.height(); canvas.drawLine(centerX, centerLineTop, centerX, centerLineBottom, mPaint); //绘制最底部手表 int watchX = centerX - mWatchBitmap.getWidth() / 2; int watchY = centerLineBottom + 40; canvas.drawBitmap(mWatchBitmap, watchX, watchY, mPaint); //绘制刚开始的加载的旋转动画 if (mDrawLoading) { canvas.save(); canvas.rotate(degree, centerX, centerY); Shader mShader = new SweepGradient(centerX, centerY, transparentWhite, Color.WHITE); mPaint.setStrokeWidth(1); mPaint.setShader(mShader); mPaint.setStyle(Paint.Style.STROKE); int loadingRadius = (int) (0.65 * getMeasuredWidth() / 2); RectF loadingCircle = new RectF(centerX - loadingRadius, centerY - loadingRadius, centerX + loadingRadius, centerY + loadingRadius); Path loadingPath = new Path(); float loadingLeft = loadingCircle.left, loadingTop = loadingCircle.top, loadingRight = loadingCircle.right, loadingBottom = loadingCircle.bottom; for (int i = 0; i < 20; i++) { int value = mRandom.nextInt(25); int sed = mRandom.nextInt(3); loadingCircle.left = loadingLeft + value + sed; loadingCircle.top = loadingTop + value - sed; loadingCircle.right = loadingRight - value + sed; loadingCircle.bottom = loadingBottom - value - sed; loadingPath.addArc(loadingCircle, 40, 320); } canvas.drawPath(loadingPath, mPaint); loadingPath.reset(); int decorPointX = centerX + loadingRadius; int decorPointY = centerY + 5; int tempX, tempY; for (int i = 0; i < 10; i++) { int value0 = mRandom.nextInt(i + 20); int value = i * 2 + mRandom.nextInt(i + 20); tempX = decorPointX - value0; tempY = decorPointY - i * 2 - value; loadingPath.addCircle(tempX, tempY, 5, Path.Direction.CCW); } mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(loadingPath, mPaint); mPaint.setShader(null); canvas.restore(); } //绘制外轮廓 if (mDrawOut) { int outRadius = (int) ((0.65 + 0.15 * getPercent()) * getMeasuredWidth() / 2); mPaint.setStrokeWidth(25); mPaint.setStyle(Paint.Style.STROKE); canvas.drawCircle(centerX, centerY, outRadius, mPaint); } //绘制内轮廓 if (mDrawInner) { mPaint.setStrokeWidth(5); int innerRadius = (int) (0.55 * getMeasuredWidth() / 2); int startRunAngle = -90, runedAngle = 270, startUnRunAngle = startRunAngle + runedAngle, unRunedAngle = 360 - runedAngle; RectF innerCircle = new RectF(centerX - innerRadius, centerY - innerRadius, centerX + innerRadius, centerY + innerRadius); PathEffect effects = new DashPathEffect(new float[]{2, 4}, 1); Path unRunPath = new Path(); unRunPath.addArc(innerCircle, startUnRunAngle, unRunedAngle); mPaint.setPathEffect(effects); canvas.drawPath(unRunPath, mPaint); mPaint.setPathEffect(null); mPaint.setColor(Color.WHITE); Path runedPath = new Path(); runedPath.addArc(innerCircle, startRunAngle, runedAngle); canvas.drawPath(runedPath, mPaint); } } private float getPercent() { return Math.abs(getTranslationY()) / maxMove; } @Override public void setTranslationY(float translationY) { super.setTranslationY(translationY); invalidate(); } @SuppressWarnings("unused") public void setDegree(int degree) { this.degree = degree; invalidate(); } public abstract class AnimatorListener implements Animator.AnimatorListener { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } } } ================================================ FILE: app/src/main/java/com/keyboard3/hencoderProduct/ruler/RulerLayout.java ================================================ package com.keyboard3.hencoderProduct.ruler; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.widget.Button; import android.widget.RelativeLayout; import com.keyboard3.hencoderProduct.R; public class RulerLayout extends RelativeLayout { public RulerLayout(Context context) { super(context); } public RulerLayout(Context context, AttributeSet attrs) { super(context, attrs); } public RulerLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } } ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/filpboard_layout.xml ================================================