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
================================================
================================================
FILE: app/src/main/res/layout/fragment_page.xml
================================================
================================================
FILE: app/src/main/res/layout/like_layout.xml
================================================
================================================
FILE: app/src/main/res/layout/move_layout.xml
================================================
================================================
FILE: app/src/main/res/layout/ruler_layout.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#FF4081
================================================
FILE: app/src/main/res/values/strings.xml
================================================
HenCoder参赛作品by keyboard3
Flipboard翻页
即刻点赞
薄荷健康尺
animate
小米运动
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/test/java/com/keyboard3/hencoderProduct/ExampleUnitTest.java
================================================
package com.keyboard3.hencoderProduct;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see Testing documentation
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.qihoo360.replugin:replugin-plugin-gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
google()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Fri Oct 13 16:17:40 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -relativeX "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -relativeX "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "relativeX%~1" == "relativeX" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: ruler/.gitignore
================================================
/build
================================================
FILE: ruler/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 25
buildToolsVersion '26.0.2'
defaultConfig {
minSdkVersion 16
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'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:25.3.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
================================================
FILE: ruler/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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: ruler/src/androidTest/java/com/keyboard3/ExampleInstrumentedTest.java
================================================
package com.keyboard3;
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.*;
/**
* Instrumented 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.keyboard3.test", appContext.getPackageName());
}
}
================================================
FILE: ruler/src/main/AndroidManifest.xml
================================================
================================================
FILE: ruler/src/main/java/com/keyboard3/BooheeRuler.java
================================================
package com.keyboard3;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 用于包着尺子的外壳,用于画选取光标、外壳
*/
public class BooheeRuler extends ViewGroup {
private final String TAG = "ruler";
private Context mContext;
//尺子Style定义
public static final int TOP_LAYOUT = 1, BOTTOM_LAYOUT = 3, LEFT_LAYOUT = 0, RIGHT_LAYOUT = 2;
@IntDef({TOP_LAYOUT, BOTTOM_LAYOUT, LEFT_LAYOUT, RIGHT_LAYOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface RulerStyle {
}
private @BooheeRuler.RulerStyle
int mStyle = TOP_LAYOUT;
//内部的尺子
private InnerRuler mInnerRuler;
//最小最大刻度值(以0.1kg为单位)
private int mMinScale = 464, mMaxScale = 2000;
//光标宽度、高度
private int mCursorWidth = 8, mCursorHeight = 70;
//大小刻度的长度
private int mSmallScaleLength = 30, mBigScaleLength = 60;
//大小刻度的粗细
private int mSmallScaleWidth = 3, mBigScaleWidth = 5;
//数字字体大小
private int mTextSize = 28;
//数字Text距离顶部高度
private int mTextMarginHead = 120;
//刻度间隔
private int mInterval = 18;
private @IdRes
int mTargetRulerNumer;
//数字Text颜色
private
@ColorInt
int mTextColor = getResources().getColor(R.color.colorLightBlack);
//刻度颜色
private
@ColorInt
int mScaleColor = getResources().getColor(R.color.colorGray);
//初始的当前刻度
private float mCurrentScale = 0;
/**
* 一格大刻度多少格小刻度
*/
private int mCount = 10;
/**
* 光标drawable
*/
private Drawable mCursorDrawable;
/**
* 尺子两端的padding
*/
private int mPaddingHeadAndEnd = 0;
private int mPaddingLeft = 0, mPaddingTop = 0, mPaddingRight = 0, mPaddingBottom = 0;
/**
* 尺子背景
*/
private Drawable mRulerBackGround;
private int mRulerBackGroundColor = getResources().getColor(R.color.colorDirtyWithe);
/**
* 是否启用边缘效应
*/
private boolean mCanEdgeEffect = true;
/**
* 边缘颜色
*/
private @ColorInt
int mEdgeColor = getResources().getColor(R.color.colorForgiven);
public BooheeRuler(Context context) {
super(context);
initRuler(context);
}
public BooheeRuler(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
initRuler(context);
}
public BooheeRuler(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initRuler(context);
}
@SuppressWarnings("WrongConstant")
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BooheeRuler, 0, 0);
mMinScale = typedArray.getInteger(R.styleable.BooheeRuler_minScale, mMinScale);
mMaxScale = typedArray.getInteger(R.styleable.BooheeRuler_maxScale, mMaxScale);
mCursorWidth = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_cursorWidth, mCursorWidth);
mCursorHeight = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_cursorHeight, mCursorHeight);
mSmallScaleWidth = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_smallScaleWidth, mSmallScaleWidth);
mSmallScaleLength = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_smallScaleLength, mSmallScaleLength);
mBigScaleWidth = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_bigScaleWidth, mBigScaleWidth);
mBigScaleLength = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_bigScaleLength, mBigScaleLength);
mTextSize = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_numberTextSize, mTextSize);
mTextMarginHead = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_textMarginHead, mTextMarginHead);
mInterval = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_scaleInterval, mInterval);
mTextColor = typedArray.getColor(R.styleable.BooheeRuler_numberTextColor, mTextColor);
mScaleColor = typedArray.getColor(R.styleable.BooheeRuler_scaleColor, mScaleColor);
mCurrentScale = typedArray.getFloat(R.styleable.BooheeRuler_currentScale, (mMaxScale + mMinScale) / 2);
mCount = typedArray.getInt(R.styleable.BooheeRuler_count, mCount);
mCursorDrawable = typedArray.getDrawable(R.styleable.BooheeRuler_cursorDrawable);
mTargetRulerNumer = typedArray.getResourceId(R.styleable.BooheeRuler_targetRulerNumber, 0);
if (mCursorDrawable == null) {
mCursorDrawable = getResources().getDrawable(R.drawable.cursor_shape);
}
mPaddingHeadAndEnd = typedArray.getDimensionPixelSize(R.styleable.BooheeRuler_paddingStartAndEnd, mPaddingHeadAndEnd);
mStyle = typedArray.getInt(R.styleable.BooheeRuler_rulerStyle, mStyle);
mRulerBackGround = typedArray.getDrawable(R.styleable.BooheeRuler_rulerBackGround);
if (mRulerBackGround == null) {
mRulerBackGroundColor = typedArray.getColor(R.styleable.BooheeRuler_rulerBackGround, mRulerBackGroundColor);
}
mCanEdgeEffect = typedArray.getBoolean(R.styleable.BooheeRuler_canEdgeEffect, mCanEdgeEffect);
mEdgeColor = typedArray.getColor(R.styleable.BooheeRuler_edgeColor, mEdgeColor);
typedArray.recycle();
}
private void initRuler(Context context) {
mContext = context;
mInnerRuler = new InnerRuler(context, this, mStyle);
paddingHeadAndEnd();
//设置全屏,加入InnerRuler
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mInnerRuler.setLayoutParams(layoutParams);
addView(mInnerRuler);
//设置ViewGroup可画
setWillNotDraw(false);
initDrawable();
initRulerBackground();
}
private void initRulerBackground() {
if (mRulerBackGround != null) {
mInnerRuler.setBackground(mRulerBackGround);
} else {
mInnerRuler.setBackgroundColor(mRulerBackGroundColor);
}
}
/**
* 在宽高初始化之后定义光标Drawable的边界
*/
private void initDrawable() {
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
switch (mStyle) {
case TOP_LAYOUT:
mCursorDrawable.setBounds((getWidth() - mCursorWidth) / 2, 0
, (getWidth() + mCursorWidth) / 2, mCursorHeight);
break;
case BOTTOM_LAYOUT:
mCursorDrawable.setBounds((getWidth() - mCursorWidth) / 2, getHeight() - mCursorHeight
, (getWidth() + mCursorWidth) / 2, getHeight());
break;
case LEFT_LAYOUT:
mCursorDrawable.setBounds(0, (getHeight() - mCursorWidth) / 2
, mCursorHeight, (getHeight() + mCursorWidth) / 2);
break;
case RIGHT_LAYOUT:
mCursorDrawable.setBounds(getWidth() - mCursorHeight, (getHeight() - mCursorWidth) / 2
, getWidth(), (getHeight() + mCursorWidth) / 2);
break;
default:
}
return false;
}
});
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//画中间的选定光标,要在这里画,因为dispatchDraw()执行在onDraw()后面,这样子光标才能不被尺子的刻度遮蔽
mCursorDrawable.draw(canvas);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
mInnerRuler.layout(mPaddingLeft, mPaddingTop, r - l - mPaddingRight, b - t - mPaddingBottom);
}
}
private void paddingHeadAndEnd() {
mPaddingBottom = mPaddingTop = InnerRuler.isVerticalRuler(mStyle) ? mPaddingHeadAndEnd : 0;
mPaddingRight = mPaddingLeft = InnerRuler.isVerticalRuler(mStyle) ? 0 : mPaddingHeadAndEnd;
}
/**
* 设置回调
*
* @param rulerCallback
*/
public void addCallback(RulerCallback rulerCallback) {
mInnerRuler.addRulerCallback(rulerCallback);
}
/**
* 设置当前进度
*
* @param currentScale
*/
public void setCurrentScale(float currentScale) {
mCurrentScale = currentScale;
mInnerRuler.setCurrentScale(currentScale);
}
/**
* 如果控件尺寸变化,中间光标的位置也要重新定义
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initDrawable();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mTargetRulerNumer != 0) {
View view = getRootView().findViewById(mTargetRulerNumer);
if (view instanceof RulerCallback) {
RulerCallback callback = (RulerCallback) view;
addCallback(callback);
}
}
}
public int getEdgeColor() {
return mEdgeColor;
}
/**
* 设置能否使用边缘效果
*
* @param canEdgeEffect
*/
public void setCanEdgeEffect(boolean canEdgeEffect) {
this.mCanEdgeEffect = canEdgeEffect;
mInnerRuler.initEdgeEffects();
}
public boolean canEdgeEffect() {
return mCanEdgeEffect;
}
public float getCurrentScale() {
return mCurrentScale;
}
public void setMinScale(int minScale) {
this.mMinScale = minScale;
}
public int getMinScale() {
return mMinScale;
}
public void setMaxScale(int maxScale) {
this.mMaxScale = maxScale;
}
public int getMaxScale() {
return mMaxScale;
}
public void setCursorWidth(int cursorWidth) {
this.mCursorWidth = cursorWidth;
}
public int getCursorWidth() {
return mCursorWidth;
}
public void setCursorHeight(int cursorHeight) {
this.mCursorHeight = cursorHeight;
}
public int getCursorHeight() {
return mCursorHeight;
}
public void setBigScaleLength(int bigScaleLength) {
this.mBigScaleLength = bigScaleLength;
}
public int getBigScaleLength() {
return mBigScaleLength;
}
public void setBigScaleWidth(int bigScaleWidth) {
this.mBigScaleWidth = bigScaleWidth;
}
public int getBigScaleWidth() {
return mBigScaleWidth;
}
public void setSmallScaleLength(int smallScaleLength) {
this.mSmallScaleLength = smallScaleLength;
}
public int getSmallScaleLength() {
return mSmallScaleLength;
}
public void setSmallScaleWidth(int smallScaleWidth) {
this.mSmallScaleWidth = smallScaleWidth;
}
public int getSmallScaleWidth() {
return mSmallScaleWidth;
}
public void setTextMarginTop(int textMarginTop) {
this.mTextMarginHead = textMarginTop;
}
public int getTextMarginHead() {
return mTextMarginHead;
}
public void setTextSize(int textSize) {
this.mTextSize = textSize;
}
public int getTextSize() {
return mTextSize;
}
public void setInterval(int interval) {
this.mInterval = interval;
}
public int getInterval() {
return mInterval;
}
public int getTextColor() {
return mTextColor;
}
public int getScaleColor() {
return mScaleColor;
}
public void setCount(int mCount) {
this.mCount = mCount;
}
public int getCount() {
return mCount;
}
}
================================================
FILE: ruler/src/main/java/com/keyboard3/InnerRuler.java
================================================
package com.keyboard3;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.os.Build;
import android.support.annotation.Px;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.EdgeEffect;
import android.widget.OverScroller;
import java.util.ArrayList;
import java.util.List;
/**
* 内部尺子抽象类
*/
public class InnerRuler extends View {
protected Context mContext;
protected BooheeRuler mParent;
protected Paint mSmallScalePaint, mBigScalePaint, mTextPaint, mOutLinePaint;
/**
* 当前刻度值
*/
protected float mCurrentScale = 0;
/**
* 最大刻度数
*/
protected int mMaxLength = 0;
/**
* 长度、最小可滑动值、最大可滑动值
*/
protected int mLength, mMinPosition = 0, mMaxPosition = 0;
/**
* 控制滑动
*/
protected OverScroller mOverScroller;
/**
* 一格大刻度多少格小刻度
*/
protected int mCount = 10;
/**
* 提前刻画量
*/
protected int mDrawOffset;
/**
* 速度获取
*/
protected VelocityTracker mVelocityTracker;
/**
* 惯性最大最小速度
*/
protected int mMaximumVelocity, mMinimumVelocity;
/**
* 回调接口
*/
protected List mRulerCallbacks = new ArrayList<>();
/**
* 边界效果
*/
protected EdgeEffect mStartEdgeEffect, mEndEdgeEffect;
/**
* 边缘效应长度
*/
protected int mEdgeLength;
int flag;
private float mLastY;
private float mLastX;
public InnerRuler(Context context, BooheeRuler booheeRuler, int flag) {
super(context);
mParent = booheeRuler;
this.flag = flag;
init(context);
}
private void init(Context context) {
mContext = context;
mMaxLength = mParent.getMaxScale() - mParent.getMinScale();
mCurrentScale = mParent.getCurrentScale();
mCount = mParent.getCount();
mDrawOffset = mCount * mParent.getInterval() / 2;
initPaints();
mOverScroller = new OverScroller(mContext);
//配置速度
mVelocityTracker = VelocityTracker.obtain();
mMaximumVelocity = ViewConfiguration.get(context)
.getScaledMaximumFlingVelocity();
mMinimumVelocity = ViewConfiguration.get(context)
.getScaledMinimumFlingVelocity();
initEdgeEffects();
//第一次进入,跳转到设定刻度
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
scroll2Scale(mCurrentScale);
}
});
checkAPILevel();
}
private void initPaints() {
mSmallScalePaint = new Paint();
mSmallScalePaint.setStrokeWidth(mParent.getSmallScaleWidth());
mSmallScalePaint.setColor(mParent.getScaleColor());
mSmallScalePaint.setStrokeCap(Paint.Cap.ROUND);
mBigScalePaint = new Paint();
mBigScalePaint.setColor(mParent.getScaleColor());
mBigScalePaint.setStrokeWidth(mParent.getBigScaleWidth());
mBigScalePaint.setStrokeCap(Paint.Cap.ROUND);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(mParent.getTextColor());
mTextPaint.setTextSize(mParent.getTextSize());
mTextPaint.setTextAlign(Paint.Align.CENTER);
mOutLinePaint = new Paint();
mOutLinePaint.setStrokeWidth(0);
mOutLinePaint.setColor(mParent.getScaleColor());
}
/**
* 初始化边缘效果
*/
public void initEdgeEffects() {
if (mParent.canEdgeEffect()) {
if (mStartEdgeEffect == null || mEndEdgeEffect == null) {
mStartEdgeEffect = new EdgeEffect(mContext);
mEndEdgeEffect = new EdgeEffect(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mStartEdgeEffect.setColor(mParent.getEdgeColor());
mEndEdgeEffect.setColor(mParent.getEdgeColor());
}
mEdgeLength = mParent.getCursorHeight() + mParent.getInterval() * mParent.getCount();
}
}
}
private void checkAPILevel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(LAYER_TYPE_NONE, null);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
this.mRulerCallbacks = null;
}
/**
* 设置尺子当前刻度
*
* @param currentScale
*/
public void setCurrentScale(float currentScale) {
this.mCurrentScale = currentScale;
scroll2Scale(mCurrentScale);
}
public void addRulerCallback(RulerCallback RulerCallback) {
mRulerCallbacks.add(RulerCallback);
}
public float getCurrentScale() {
return mCurrentScale;
}
//======绘制逻辑=======
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawRulerBody(canvas, flag);
}
protected void drawRulerBody(Canvas canvas, int flag) {
float location = 0, length;
if (isVerticalRuler(flag)) {
//刻度是水平的 Y是固定的
location = getScrollY();
length = canvas.getHeight();
} else {
//刻度是竖直的 x是固定的
location = getScrollX();
length = canvas.getWidth();
}
int start = (int) ((location - mDrawOffset) / mParent.getInterval() + mParent.getMinScale());
int end = (int) ((location + length + mDrawOffset) / mParent.getInterval() + mParent.getMinScale());
for (int i = start; i <= end; i++) {
if (i >= mParent.getMinScale() && i <= mParent.getMaxScale()) {
if (i % mCount == 0) {
PointF[] line = getLine(mParent, flag, mParent.getBigScaleLength(), i, canvas.getHeight(), canvas.getWidth());
canvas.drawLine(line[0].x, line[0].y, line[1].x, line[1].y, mBigScalePaint);
PointF textEndPoint = getTextPoint(mParent, flag, i, canvas.getHeight(), canvas.getWidth());
canvas.drawText(String.valueOf(i / 10), textEndPoint.x, textEndPoint.y, mTextPaint);
} else {
PointF[] line = getLine(mParent, flag, mParent.getSmallScaleLength(), i, canvas.getHeight(), canvas.getWidth());
canvas.drawLine(line[0].x, line[0].y, line[1].x, line[1].y, mSmallScalePaint);
}
}
}
//画轮廓线
PointF[] outLine = getOutLine(this, flag, canvas.getHeight(), canvas.getWidth());
canvas.drawLine(outLine[0].x, outLine[0].y, outLine[1].x, outLine[1].y, mOutLinePaint);
PointF[] outLine2 = getOutLine(this, getNegativeFlag(flag), canvas.getHeight(), canvas.getWidth());
canvas.drawLine(outLine2[0].x, outLine2[0].y, outLine2[1].x, outLine2[1].y, mOutLinePaint);
PointF[] outLine3 = getOutLeftOrRightLine(this, flag, true, canvas.getHeight(), canvas.getWidth());
canvas.drawLine(outLine3[0].x, outLine3[0].y, outLine3[1].x, outLine3[1].y, mOutLinePaint);
PointF[] outLine4 = getOutLeftOrRightLine(this, flag, false, canvas.getHeight(), canvas.getWidth());
canvas.drawLine(outLine4[0].x, outLine4[0].y, outLine4[1].x, outLine4[1].y, mOutLinePaint);
}
protected static boolean isVerticalRuler(int flag) {
return flag == 0 || flag == 2;
}
protected static int getNegativeFlag(int flag) {
return (flag + 2) % 4;
}
private static PointF getTextPoint(BooheeRuler mParent, int flag, float location, int height, int width) {
PointF pointF = new PointF();
float locationY = 0, locationX = 0;
if (isVerticalRuler(flag)) {
//刻度是水平的 Y是固定的
locationY = (location - mParent.getMinScale()) * mParent.getInterval();
} else {
//刻度是竖直的 x是固定的
locationX = (location - mParent.getMinScale()) * mParent.getInterval();
}
switch (flag) {
case BooheeRuler.LEFT_LAYOUT:
pointF.x = mParent.getTextMarginHead();
pointF.y = locationY + mParent.getTextSize() / 2;
break;
case BooheeRuler.TOP_LAYOUT:
pointF.x = locationX;
pointF.y = mParent.getTextMarginHead();
break;
case BooheeRuler.RIGHT_LAYOUT:
pointF.x = width - mParent.getTextMarginHead();
pointF.y = locationY + mParent.getTextSize() / 2;
break;
case BooheeRuler.BOTTOM_LAYOUT:
pointF.x = locationX;
pointF.y = height - mParent.getTextMarginHead();
break;
default:
}
return pointF;
}
private static PointF[] getLine(BooheeRuler mParent, int flag, int length, float location, int height, int width) {
PointF startPoint = new PointF();
PointF stopPoint = new PointF();
float locationY = 0, locationX = 0;
if (isVerticalRuler(flag)) {
//刻度是水平的 Y是固定的
locationY = (location - mParent.getMinScale()) * mParent.getInterval();
} else {//水平的尺子
//刻度是竖直的 x是固定的
locationX = (location - mParent.getMinScale()) * mParent.getInterval();
}
switch (flag) {
case BooheeRuler.LEFT_LAYOUT:
startPoint.y = locationY;
stopPoint.x = length;
stopPoint.y = locationY;
break;
case BooheeRuler.TOP_LAYOUT:
startPoint.x = locationX;
stopPoint.x = locationX;
stopPoint.y = length;
break;
case BooheeRuler.RIGHT_LAYOUT:
startPoint.x = width - length;
startPoint.y = locationY;
stopPoint.x = width;
stopPoint.y = locationY;
break;
case BooheeRuler.BOTTOM_LAYOUT:
startPoint.x = locationX;
startPoint.y = height - length;
stopPoint.x = locationX;
stopPoint.y = height;
break;
default:
}
return new PointF[]{startPoint, stopPoint};
}
private static PointF[] getOutLine(View contentView, int flag, int height, int width) {
PointF startPoint = new PointF();
PointF stopPoint = new PointF();
float locationY = 0, locationX = 0;
if (isVerticalRuler(flag)) {
//边界线是竖直的 竖直滚动的起始位置
locationY = contentView.getScrollY();
} else {
//边界线是水平的 水平滚动的起始位置
locationX = contentView.getScrollX();
}
switch (flag) {
case BooheeRuler.LEFT_LAYOUT:
startPoint.y = locationY;
stopPoint.y = locationY + height;
break;
case BooheeRuler.TOP_LAYOUT:
startPoint.x = locationX;
stopPoint.x = locationX + width;
break;
case BooheeRuler.RIGHT_LAYOUT:
startPoint.y = locationY;
stopPoint.y = locationY + height;
startPoint.x = width - 0.1F;
stopPoint.x = width - 0.1F;
break;
case BooheeRuler.BOTTOM_LAYOUT:
startPoint.x = locationX;
startPoint.y = height - 0.1F;
stopPoint.x = locationX + width;
stopPoint.y = height - 0.1F;
break;
default:
}
return new PointF[]{startPoint, stopPoint};
}
private static PointF[] getOutLeftOrRightLine(View contentView, int flag, boolean isleft, int height, int width) {
PointF startPoint = new PointF();
PointF stopPoint = new PointF();
float locationY = 0, locationX = 0;
if (isVerticalRuler(flag)) {
//边界线是竖直的 竖直滚动的起始位置
locationY = contentView.getScrollY();
} else {
//边界线是水平的 水平滚动的起始位置
locationX = contentView.getScrollX();
}
switch (flag) {
case BooheeRuler.LEFT_LAYOUT:
if (isleft) {
startPoint.y = locationY;
stopPoint.y = locationY;
stopPoint.x = width;
} else {
startPoint.y = locationY + height - 0.1F;
stopPoint.y = locationY + height - 0.1F;
stopPoint.x = width;
}
break;
case BooheeRuler.TOP_LAYOUT:
if (isleft) {
startPoint.x = locationX;
stopPoint.x = locationX;
stopPoint.y = height;
} else {
startPoint.x = locationX + width - 0.1F;
stopPoint.x = locationX + width - 0.1F;
stopPoint.y = height;
}
break;
case BooheeRuler.RIGHT_LAYOUT:
if (isleft) {
startPoint.y = locationY;
stopPoint.y = locationY;
startPoint.x = width - 0.1F;
} else {
startPoint.y = locationY + height - 0.1F;
stopPoint.y = locationY + height - 0.1F;
startPoint.x = width - 0.1F;
}
break;
case BooheeRuler.BOTTOM_LAYOUT:
if (isleft) {
startPoint.x = locationX;
startPoint.y = height - 0.1F;
stopPoint.x = locationX;
} else {
startPoint.x = locationX + width - 0.1F;
startPoint.y = height - 0.1F;
stopPoint.x = locationX + width - 0.1F;
}
break;
default:
}
return new PointF[]{startPoint, stopPoint};
}
//======滑动事件逻辑======
//获取控件宽高,设置相应信息
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mLength = (mParent.getMaxScale() - mParent.getMinScale()) * mParent.getInterval();
int mHalf = (isVerticalRuler(flag) ? h : w) / 2;
mMinPosition = -mHalf;
mMaxPosition = mLength - mHalf;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float currentY = event.getY();
float currentX = event.getX();
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!mOverScroller.isFinished()) {
mOverScroller.abortAnimation();
}
mLastY = currentY;
mLastX = currentX;
break;
case MotionEvent.ACTION_MOVE:
float moveX = isVerticalRuler(flag) ? 0 : mLastX - currentX;
float moveY = isVerticalRuler(flag) ? mLastY - currentY : 0;
scrollBy((int) moveX, (int) moveY);
mLastY = currentY;
mLastX = currentX;
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int velocityY = 0, velocityX = 0;
boolean startScroll = false;
if (isVerticalRuler(flag)) {
velocityY = (int) mVelocityTracker.getYVelocity();
startScroll = Math.abs(velocityY) > mMinimumVelocity;
} else {
velocityX = (int) mVelocityTracker.getXVelocity();
startScroll = Math.abs(velocityX) > mMinimumVelocity;
}
if (startScroll) {
fling(isVerticalRuler(flag), -velocityX, -velocityY);
} else {
scrollBackToCurrentScale(isVerticalRuler(flag));
}
//VelocityTracker回收
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
//releaseEdgeEffects();
break;
case MotionEvent.ACTION_CANCEL:
if (!mOverScroller.isFinished()) {
mOverScroller.abortAnimation();
}
scrollBackToCurrentScale(isVerticalRuler(flag));
//VelocityTracker回收
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
//releaseEdgeEffects();
break;
default:
}
return true;
}
private void fling(boolean isVertical, int vx, int vy) {
int startX = 0, startY = 0, velocityX = 0, velocityY = 0, minX = 0, minY = 0, maxX = 0, maxY = 0;
startX = isVertical ? 0 : getScrollX();
startY = isVertical ? getScrollY() : 0;
velocityX = isVertical ? 0 : vx;
velocityY = isVertical ? vy : 0;
int min = mMinPosition - mEdgeLength;
int max = mMaxPosition + mEdgeLength;
minX = isVertical ? 0 : min;
minY = isVertical ? 0 : max;
maxX = isVertical ? min : 0;
maxY = isVertical ? max : 0;
mOverScroller.fling(startX, startY, velocityX, velocityY, minX, minY, maxX, maxY);
invalidate();
}
@Override
public void computeScroll() {
if (mOverScroller.computeScrollOffset()) {
scrollTo(mOverScroller.getCurrX(), mOverScroller.getCurrY());
if (!mOverScroller.computeScrollOffset() && mCurrentScale != Math.round(mCurrentScale)) {
scrollBackToCurrentScale(isVerticalRuler(flag));
}
invalidate();
}
}
protected void scrollBackToCurrentScale(boolean isVertical) {
int startX = 0, startY = 0, destX, destY = 0;
startX = isVertical ? 0 : getScrollX();
startY = isVertical ? getScrollY() : 0;
destX = isVertical ? 0 : scale2ScrollXY(Math.round(mCurrentScale)) - startX;
destY = isVertical ? scale2ScrollXY(Math.round(mCurrentScale)) - startY : 0;
mOverScroller.startScroll(startX, startY, destX, destY, 1000);
invalidate();
}
protected void scroll2Scale(float scale) {
mCurrentScale = Math.round(scale);
scrollTo(0, scale2ScrollXY(mCurrentScale));
}
/**
* 将移动过程中经过的刻度显示出来
*
* @param x
* @param y
*/
@Override
public void scrollTo(@Px int x, @Px int y) {
if (isVerticalRuler(flag)) {
if (y < mMinPosition || y > mMaxPosition) {
y = mMinPosition;
}
if (y != getScrollY()) {
super.scrollTo(x, y);
}
mCurrentScale = scrollXY2Scale(y);
} else {
if (x < mMinPosition || x > mMaxPosition) {
x = mMinPosition;
}
if (x != getScrollX()) {
super.scrollTo(x, y);
}
mCurrentScale = scrollXY2Scale(x);
}
if (mRulerCallbacks != null) {
for (RulerCallback item :
mRulerCallbacks) {
item.onScaleChanging(Math.round(mCurrentScale));
}
}
}
/**
* 将滚动位置转化为刻度
*
* @param scrollXY
* @return
*/
private float scrollXY2Scale(int scrollXY) {
return ((float) (scrollXY - mMinPosition) / mLength) * mMaxLength + mParent.getMinScale();
}
/**
* 将刻度转化为移动的位置
*
* @param scale
* @return
*/
private int scale2ScrollXY(float scale) {
return (int) ((scale - mParent.getMinScale()) / mMaxLength * mLength + mMinPosition);
}
}
================================================
FILE: ruler/src/main/java/com/keyboard3/RulerCallback.java
================================================
package com.keyboard3;
/**
* Created by yany on 2017/10/16.
*/
public interface RulerCallback {
//选取刻度变化的时候回调
void onScaleChanging(float scale);
//选取刻度变化完成的时候回调
// void afterScaleChanged(float scale);
}
================================================
FILE: ruler/src/main/java/com/keyboard3/RulerNumberLayout.java
================================================
package com.keyboard3;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.IdRes;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* Created by yany on 2017/10/17.
* 用于包着显示具体数字刻度Layout
*/
public class RulerNumberLayout extends RelativeLayout implements RulerCallback {
private TextView tv_scale, tv_kg;
//字体大小
private float mScaleTextSize = 80, mKgTextSize = 40;
//字体颜色
private @ColorInt
int mScaleTextColor = getResources().getColor(R.color.colorForgiven);
private @ColorInt
int mKgTextColor = getResources().getColor(R.color.colorForgiven);
@IdRes
int mTargetRuler;
//kg单位文字
private String mUnitText = "kg";
public RulerNumberLayout(Context context) {
super(context);
init(context);
}
public RulerNumberLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
init(context);
}
public RulerNumberLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RulerNumberLayout, 0, 0);
mScaleTextSize = typedArray.getDimension(R.styleable.RulerNumberLayout_scaleTextSize, mScaleTextSize);
mKgTextSize = typedArray.getDimension(R.styleable.RulerNumberLayout_kgTextSize, mKgTextSize);
mScaleTextColor = typedArray.getColor(R.styleable.RulerNumberLayout_scaleTextColor, mScaleTextColor);
mKgTextColor = typedArray.getColor(R.styleable.RulerNumberLayout_kgTextColor, mKgTextColor);
mKgTextColor = typedArray.getColor(R.styleable.RulerNumberLayout_kgTextColor, mKgTextColor);
mTargetRuler = typedArray.getResourceId(R.styleable.RulerNumberLayout_targetRuler, 0);
String text = typedArray.getString(R.styleable.RulerNumberLayout_kgUnitText);
if (text != null) {
mUnitText = text;
}
typedArray.recycle();
}
private void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.layout_kg_number, this);
tv_scale = (TextView) findViewById(R.id.tv_scale);
tv_kg = (TextView) findViewById(R.id.tv_kg);
tv_scale.setTextSize(TypedValue.COMPLEX_UNIT_PX, mScaleTextSize);
tv_scale.setTextColor(mScaleTextColor);
tv_kg.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKgTextSize);
tv_kg.setTextColor(mKgTextColor);
tv_kg.setText(mUnitText);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mTargetRuler != 0) {
View ruler = getRootView().findViewById(mTargetRuler);
if (ruler instanceof BooheeRuler) {
((BooheeRuler) ruler).addCallback(this);
}
}
}
public void bindRuler(BooheeRuler booheeRuler) {
booheeRuler.addCallback(this);
}
@Override
public void onScaleChanging(float scale) {
tv_scale.setText(String.valueOf(scale / 10));
}
}
================================================
FILE: ruler/src/main/res/drawable/cursor_shape.xml
================================================
================================================
FILE: ruler/src/main/res/layout/layout_kg_number.xml
================================================
================================================
FILE: ruler/src/main/res/values/attr.xml
================================================
================================================
FILE: ruler/src/main/res/values/colors.xml
================================================
#e2e5e2
#f6f9f6
#4bbb74
#2B2E2B
================================================
FILE: ruler/src/main/res/values/strings.xml
================================================
Ruler
================================================
FILE: ruler/src/test/java/com/keyboard3/ExampleUnitTest.java
================================================
package com.keyboard3;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see Testing documentation
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
================================================
FILE: settings.gradle
================================================
include ':app', ':ruler'