();
28 | datas.append(0, R.mipmap.aaaa);
29 | datas.append(1, R.mipmap.bbbbb);
30 | datas.append(2, R.mipmap.ccccc);
31 | datas.append(3, R.mipmap.dddd);
32 | datas.append(4, R.mipmap.eeeee);
33 |
34 |
35 | galleryView.setDatas(datas);
36 |
37 |
38 |
39 |
40 |
41 | findViewById(R.id.last).setOnClickListener(new View.OnClickListener() {
42 | @Override
43 | public void onClick(View v) {
44 | galleryView.last();
45 | }
46 | });
47 | findViewById(R.id.next).setOnClickListener(new View.OnClickListener() {
48 | @Override
49 | public void onClick(View v) {
50 | galleryView.start();
51 |
52 | }
53 | });
54 | }
55 |
56 | @Override
57 | public boolean onKeyDown(int keyCode, KeyEvent event) {
58 | switch (keyCode) {
59 | case KeyEvent.KEYCODE_DPAD_LEFT:
60 | galleryView.last();
61 | break;
62 | case KeyEvent.KEYCODE_DPAD_RIGHT:
63 | galleryView.next();
64 |
65 | break;
66 | case KeyEvent.KEYCODE_DPAD_UP:
67 | galleryView.start();
68 | break;
69 | case KeyEvent.KEYCODE_DPAD_DOWN:
70 | galleryView.stop();
71 |
72 | break;
73 | }
74 | return super.onKeyDown(keyCode, event);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/RoationView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Matrix;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PathMeasure;
10 | import android.graphics.drawable.BitmapDrawable;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 |
14 | import com.xk.customview.R;
15 |
16 | /**
17 | * Created by xuekai on 2017/2/20.
18 | */
19 |
20 | public class RoationView extends View {
21 |
22 | private Paint paint;
23 | private float[] pos = new float[2];
24 | private float[] tan = new float[2];
25 |
26 | private int progress =1;
27 |
28 | public RoationView(Context context, AttributeSet attrs) {
29 | super(context, attrs);
30 | init();
31 | }
32 |
33 | private void init() {
34 | paint = new Paint();
35 | paint.setColor(0xff000000);
36 | paint.setStrokeWidth(30);
37 | paint.setStyle(Paint.Style.FILL);
38 | }
39 |
40 | @Override
41 | protected void onDraw(Canvas canvas) {
42 | canvas.translate(getWidth() / 2, getHeight() / 2);
43 | Path path = new Path();
44 | path.addCircle(0, 0, 200, Path.Direction.CW);
45 | canvas.drawCircle(0, 0, 200, new Paint());
46 |
47 | BitmapDrawable drawable = (BitmapDrawable) getResources().getDrawable(R.mipmap.jiantou);
48 | Bitmap bitmap = drawable.getBitmap();
49 |
50 | PathMeasure pathMeasure = new PathMeasure(path, false);
51 | progress += 3;
52 | //1200 大约是周长,随便一估,主要是逻辑
53 | if (progress > 1200) {
54 | progress = 1;
55 | }
56 |
57 | // pathMeasure.getPosTan(progress, pos, tan);
58 | // Matrix matrix = new Matrix();
59 | // matrix.postRotate((float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI), bitmap.getWidth() / 2, bitmap.getHeight() / 2);
60 | // matrix.postTranslate(pos[0]-bitmap.getWidth()/2, pos[1]-bitmap.getHeight()/2);
61 |
62 |
63 | Matrix matrix = new Matrix();
64 | boolean matrix1 = pathMeasure.getMatrix(progress, matrix, PathMeasure.POSITION_MATRIX_FLAG|PathMeasure.TANGENT_MATRIX_FLAG);;
65 | matrix.preTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);
66 |
67 | canvas.drawBitmap(bitmap, matrix, new Paint());
68 |
69 |
70 | postInvalidate();
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/strategyanimation/StrategyState.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.strategyanimation;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.support.v4.media.MediaBrowserCompat;
8 | import android.view.View;
9 |
10 | import com.xk.customview.R;
11 |
12 | /**
13 | * Created by xuekai on 2019/3/27.
14 | */
15 | public abstract class StrategyState {
16 | /**
17 | * 颜色
18 | */
19 | private final int[] colorArray;
20 | private final int width;
21 | private final int height;
22 | private final Paint ballPaint;
23 | private final Paint bgPaint;
24 | //画布初始的角度,用来实现旋转效果
25 | protected static int canvasAngle;
26 | //白色背景的半径
27 | protected int bgRadius = -1;
28 |
29 | protected ValueAnimator animator;
30 |
31 | public StrategyState(View view, int width, int height) {
32 | colorArray = view.getContext().getResources().getIntArray(R.array.splash_circle_colors);
33 | this.width = width;
34 | this.height = height;
35 | ballPaint = new Paint();
36 | bgPaint = new Paint();
37 | bgPaint.setColor(Color.WHITE);
38 | }
39 |
40 | /**
41 | * 小球所处的圆的半径
42 | */
43 | protected int radius = 100;
44 |
45 | public abstract void drawState(Canvas canvas);
46 |
47 |
48 | protected void drawBall(Canvas canvas) {
49 | int length = colorArray.length;
50 | float perAngle = (360f / length);
51 | canvas.save();
52 | canvas.translate(width / 2, height / 2);
53 | canvas.rotate(canvasAngle);
54 |
55 | for (int color : colorArray) {
56 | ballPaint.setColor(color);
57 | //小球半径
58 | int ballRadius = 20;
59 | canvas.drawCircle(radius, 0, ballRadius, ballPaint);
60 | canvas.rotate(perAngle);
61 | }
62 | canvas.restore();
63 | }
64 |
65 | protected void drawBg(Canvas canvas) {
66 | if (bgRadius >= 0) {
67 | bgPaint.setStrokeWidth(height);
68 | bgPaint.setColor(Color.WHITE);
69 | bgPaint.setStyle(Paint.Style.STROKE);
70 | canvas.save();
71 | canvas.translate(width / 2, height / 2);
72 | canvas.drawCircle(0, 0, bgRadius, bgPaint);
73 | canvas.restore();
74 | } else {
75 | canvas.drawRect(0, 0, width, height, bgPaint);
76 | }
77 | }
78 |
79 | public void cancel() {
80 | if (animator != null) {
81 | animator.cancel();
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/revealdrawable/RevealDrawable.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.revealdrawable;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.ColorFilter;
5 | import android.graphics.Paint;
6 | import android.graphics.Rect;
7 | import android.graphics.drawable.Drawable;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.util.Log;
11 | import android.view.Gravity;
12 |
13 | /**
14 | * Created by xuekai on 2019/3/13.
15 | */
16 | public class RevealDrawable extends Drawable {
17 |
18 | Drawable iv1;
19 | Drawable iv2;
20 | private boolean revert;
21 |
22 | public RevealDrawable(Drawable iv1, Drawable iv2) {
23 | this.iv1 = iv1;
24 | this.iv2 = iv2;
25 | }
26 |
27 | @Override
28 | protected void onBoundsChange(Rect bounds) {
29 | // 定好两个Drawable图片的宽高---边界bounds
30 | iv1.setBounds(bounds);
31 | iv2.setBounds(bounds);
32 | }
33 |
34 |
35 | public void setRevert(boolean isRevert) {
36 | this.revert = isRevert;
37 | }
38 |
39 | @Override
40 | public void draw(@NonNull Canvas canvas) {
41 | Rect bounds = getBounds();
42 | Rect rect = new Rect();
43 |
44 | // level = 0, 左边裁剪全部
45 | // level = 10000, 右边裁剪全部
46 | int clipLeft = 0;
47 | clipLeft = (int) (bounds.width() - (bounds.width()) * ((mLevel) / 10000f));
48 | Drawable iv11;
49 | Drawable iv22;
50 | if (!revert) {
51 | iv11 = iv1;
52 | iv22 = iv2;
53 | } else {
54 | iv11 = iv2;
55 | iv22 = iv1;
56 | }
57 | int clipRight = bounds.width() - clipLeft;
58 | Gravity.apply(Gravity.LEFT
59 | , clipLeft, bounds.height()
60 | , getBounds(), rect);
61 | canvas.save();
62 | canvas.clipRect(rect);
63 | iv11.draw(canvas);
64 | canvas.restore();
65 | Gravity.apply(Gravity.RIGHT
66 | , clipRight, bounds.height()
67 | , getBounds(), rect);
68 | canvas.clipRect(rect);
69 | iv22.draw(canvas);
70 | }
71 |
72 | @Override
73 | public void setAlpha(int alpha) {
74 |
75 | }
76 |
77 | private int mLevel = 0;
78 |
79 | @Override
80 | protected boolean onLevelChange(int level) {
81 | if (mLevel != level) {
82 | mLevel = level;
83 | invalidateSelf();
84 | return true;
85 | }
86 | return false;
87 | }
88 |
89 | @Override
90 | public void setColorFilter(@Nullable ColorFilter colorFilter) {
91 |
92 | }
93 |
94 | @Override
95 | public int getOpacity() {
96 | return 0;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/animatorframe/MLinearLayout.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.animatorframe;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.os.Build;
6 | import android.support.annotation.Nullable;
7 | import android.support.annotation.RequiresApi;
8 | import android.util.AttributeSet;
9 | import android.util.Log;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.LinearLayout;
13 | import android.widget.ScrollView;
14 |
15 | import com.xk.customview.R;
16 |
17 | /**
18 | * Created by xuekai on 2019/3/29.
19 | */
20 | public class MLinearLayout extends LinearLayout {
21 | public MLinearLayout(Context context) {
22 | this(context, null);
23 | }
24 |
25 | public MLinearLayout(Context context, @Nullable AttributeSet attrs) {
26 | this(context, attrs, 0);
27 |
28 | }
29 |
30 | public MLinearLayout(Context context, AttributeSet attrs, int i) {
31 | super(context, attrs, i);
32 | }
33 |
34 | @RequiresApi(api = Build.VERSION_CODES.M)
35 | @Override
36 | protected void onAttachedToWindow() {
37 | super.onAttachedToWindow();
38 | ((ScrollView) getParent()).setOnScrollChangeListener(new OnScrollChangeListener() {
39 | @Override
40 | public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
41 | int childCount = getChildCount();
42 |
43 | for (int i = 0; i < childCount; i++) {
44 | MLinearLayout.MLayoutParams layoutParams = (MLayoutParams) getChildAt(i).getLayoutParams();
45 | if (layoutParams.from == 1000) {
46 | Log.i("MLinearLayout","onScrollChange-->"+scrollY);
47 | performAnimation(getChildAt(i), layoutParams.from, getChildAt(i).getTop()-scrollY);
48 | }
49 | }
50 | }
51 | });
52 | }
53 |
54 | /**
55 | * 在这个父布局中处理各个孩子的动画。 也可以重写addView,给子View套一个View,把子View的属性传递给父View,让那个布局去处理动画。
56 | * @param childAt
57 | * @param from
58 | * @param top
59 | */
60 | private void performAnimation(View childAt, int from, int top) {
61 | Log.i("MLinearLayout","performAnimation-->"+top);
62 | int height = getHeight();
63 | int i = (int) (height - top);
64 | float i1 = i * 1f / height;
65 | childAt.setTranslationX(i1 * from);
66 | }
67 |
68 | @Override
69 | public void addView(View child, ViewGroup.LayoutParams params) {
70 | super.addView(child, params);
71 | }
72 |
73 | @Override
74 | public LayoutParams generateLayoutParams(AttributeSet attrs) {
75 | return new MLayoutParams(getContext(), attrs);
76 | }
77 |
78 |
79 | class MLayoutParams extends LinearLayout.LayoutParams {
80 |
81 | public final int from;
82 |
83 | public MLayoutParams(Context c, AttributeSet attrs) {
84 | super(c, attrs);
85 | TypedArray a =
86 | c.obtainStyledAttributes(attrs, R.styleable.View);
87 |
88 | from = a.getInteger(R.styleable.View_from, 0);
89 |
90 | a.recycle();
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/revealdrawable/MScrollView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.revealdrawable;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.HorizontalScrollView;
8 | import android.widget.LinearLayout;
9 |
10 | import com.xk.customview.R;
11 |
12 |
13 | /**
14 | * Created by xuekai on 2019/3/14.
15 | */
16 | public class MScrollView extends HorizontalScrollView {
17 |
18 | private LinearLayout linearLayout;
19 |
20 | public MScrollView(Context context) {
21 | super(context);
22 | }
23 |
24 | public MScrollView(Context context, AttributeSet attrs) {
25 | super(context, attrs);
26 | init();
27 | }
28 |
29 | int imageWidth = 300;
30 | int[] drawableIds = {
31 | R.drawable.avft,
32 | R.drawable.box_stack,
33 | R.drawable.bubble_frame,
34 | R.drawable.bubbles,
35 | R.drawable.bullseye,
36 | R.drawable.circle_filled,
37 | R.drawable.circle_outline,
38 | };
39 | int[] drawableActiveIds = {
40 | R.drawable.avft_active,
41 | R.drawable.box_stack_active,
42 | R.drawable.bubble_frame_active,
43 | R.drawable.bubbles_active,
44 | R.drawable.bullseye_active,
45 | R.drawable.circle_filled_active,
46 | R.drawable.circle_outline_active,
47 | };
48 |
49 | private void init() {
50 | linearLayout = new LinearLayout(getContext());
51 | addView(linearLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, imageWidth));
52 | for (int i = 0; i < drawableIds.length; i++) {
53 | View view = new View(getContext());
54 | view.setBackgroundDrawable(new RevealDrawable(getResources().getDrawable(drawableIds[i]), getResources().getDrawable(drawableActiveIds[i])));
55 | linearLayout.addView(view, new ViewGroup.LayoutParams(new ViewGroup.LayoutParams(imageWidth, imageWidth)));
56 | }
57 | }
58 |
59 | @Override
60 | protected void onScrollChanged(int l, int t, int oldl, int oldt) {
61 | super.onScrollChanged(l, t, oldl, oldt);
62 |
63 | int left = getWidth() / 2 - imageWidth / 2;
64 | int realLeft;
65 | for (int i = 0; i < linearLayout.getChildCount(); i++) {
66 | View childAt = linearLayout.getChildAt(i);
67 | realLeft = childAt.getLeft() - l;
68 | ((RevealDrawable) childAt.getBackground()).setRevert(false);
69 | if (realLeft < left - imageWidth) {
70 | childAt.getBackground().setLevel(0);
71 | } else if (realLeft > left + imageWidth) {
72 | childAt.getBackground().setLevel(0);
73 | } else {
74 | int A;
75 | int B;
76 | if ((realLeft < left)) {
77 | A = left - imageWidth;
78 | B = left;
79 | ((RevealDrawable) childAt.getBackground()).setRevert(false);
80 | } else {
81 | A = left;
82 | B = left + imageWidth;
83 | ((RevealDrawable) childAt.getBackground()).setRevert(true);
84 | }
85 | int v = (int) ((10000f * A / (A - B) - 10000F / (A - B) * realLeft));
86 | childAt.getBackground().setLevel( v);
87 |
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/behavior/MCoordinatorLayout.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.behavior;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.view.NestedScrollingParent;
8 | import android.text.TextUtils;
9 | import android.util.AttributeSet;
10 | import android.view.View;
11 | import android.widget.FrameLayout;
12 |
13 | import com.xk.customview.R;
14 |
15 | import java.lang.reflect.Constructor;
16 | import java.lang.reflect.InvocationTargetException;
17 |
18 | /**
19 | * Created by xuekai on 2019/4/1.
20 | */
21 | public class MCoordinatorLayout extends FrameLayout implements NestedScrollingParent {
22 | public MCoordinatorLayout(@NonNull Context context) {
23 | super(context);
24 | }
25 |
26 | public MCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
27 | super(context, attrs);
28 | }
29 |
30 | public MCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
31 | super(context, attrs, defStyleAttr);
32 | }
33 |
34 | @Override
35 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
36 | return true;
37 | }
38 |
39 | @Override
40 | public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
41 |
42 | int childCount = getChildCount();
43 | for (int i = 0; i < childCount; i++) {
44 | View childAt = getChildAt(i);
45 | if (childAt.getLayoutParams() instanceof CustomLayoutParams) {
46 | CustomBehavior behavior = ((CustomLayoutParams) childAt.getLayoutParams()).behavior;
47 | if (behavior != null) {
48 | behavior.onNestedScroll(childAt,dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
49 | }
50 | }
51 | }
52 | super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
53 | }
54 |
55 | @Override
56 | public LayoutParams generateLayoutParams(AttributeSet attrs) {
57 | return new CustomLayoutParams(getContext(), attrs);
58 | }
59 |
60 |
61 | class CustomLayoutParams extends LayoutParams {
62 | CustomBehavior behavior;
63 |
64 | public CustomLayoutParams(Context context, AttributeSet attrs) {
65 | super(context, attrs);
66 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MCoordinatorLayout);
67 | String behaviorName = typedArray.getString(R.styleable.MCoordinatorLayout_behavior);
68 | if (!TextUtils.isEmpty(behaviorName)) {
69 | try {
70 | Class> behaviorClass = Class.forName(behaviorName);
71 | Constructor> constructor = behaviorClass.getConstructor(Context.class, AttributeSet.class);
72 | behavior = (CustomBehavior) constructor.newInstance(context, attrs);
73 | } catch (ClassNotFoundException e) {
74 | e.printStackTrace();
75 | } catch (NoSuchMethodException e) {
76 | e.printStackTrace();
77 | } catch (IllegalAccessException e) {
78 | e.printStackTrace();
79 | } catch (InstantiationException e) {
80 | e.printStackTrace();
81 | } catch (InvocationTargetException e) {
82 | e.printStackTrace();
83 | }
84 | }
85 |
86 | typedArray.recycle();
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_animator_frame.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
20 |
21 |
26 |
27 |
32 |
33 |
38 |
39 |
43 |
44 |
48 |
49 |
53 |
54 |
58 |
59 |
63 |
68 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/HotLabelView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.util.Log;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | /**
10 | * 仿华为分类标签,可根据宽度自动换行
11 | * 宽度:
12 | * 1.match_parent: xxx
13 | * 2.wrap_content: 按照match_parent处理
14 | * 3.xxdp:具体宽度
15 | * 高度:
16 | * 1.match_parent: xxx
17 | * 2.wrap_content: xxx
18 | *
19 | * Created by xuekai on 2017/11/2.
20 | */
21 |
22 | public class HotLabelView extends ViewGroup {
23 |
24 |
25 | public HotLabelView(Context context, AttributeSet attrs) {
26 | super(context, attrs);
27 | }
28 |
29 |
30 | @Override
31 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
32 | return new MarginLayoutParams(getContext(), attrs);
33 | }
34 |
35 | @Override
36 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
37 | MarginLayoutParams lp;
38 | int childCount = getChildCount();
39 | Log.i("HotLabelView", "onLayout-->" + l + " " + t + " " + r + " " + b);
40 | int left = 0, top = 0, right, bottom = 0;
41 | for (int i = 0; i < childCount; ++i) {
42 |
43 | final View child = getChildAt(i);
44 | lp = (MarginLayoutParams) child.getLayoutParams();
45 |
46 | if (top == 0) {
47 | top += lp.topMargin;
48 | }
49 | if (left + lp.leftMargin + child.getMeasuredWidth() < r) {//不换行
50 |
51 | } else {//换行
52 | top = bottom + lp.bottomMargin + lp.topMargin ;
53 | // top += top + lp.bottomMargin + lp.topMargin + child.getMeasuredHeight();
54 | left = 0;
55 | }
56 |
57 | left += lp.leftMargin;
58 | right = left + child.getMeasuredWidth();
59 | bottom = top + child.getMeasuredHeight();
60 | child.layout(left, top, right, bottom);
61 |
62 | left = left + child.getMeasuredWidth() + lp.rightMargin;
63 | }
64 | }
65 |
66 | @Override
67 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
68 | MarginLayoutParams lp = null;
69 |
70 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
71 | int widthMode = MeasureSpec.getMode(widthMeasureSpec);
72 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
73 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
74 |
75 | int childCount = getChildCount();
76 | int maxHeight = 0;
77 | int currentWidth = 0;
78 | int maxWidth = 0;
79 | for (int i = 0; i < childCount; ++i) {
80 | final View child = getChildAt(i);
81 | lp = (MarginLayoutParams) child.getLayoutParams();
82 |
83 | measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
84 |
85 | if ((widthSize - currentWidth) < (lp.leftMargin + lp.rightMargin + child.getMeasuredWidth())) {
86 | currentWidth = 0;
87 | maxHeight += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
88 | } else {
89 | currentWidth = currentWidth + lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
90 | }
91 |
92 | maxWidth= Math.max(maxWidth,currentWidth);
93 | }
94 | maxHeight += getChildAt(0).getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
95 |
96 | int heightResult = 0;
97 | int widthResult = widthSize;
98 |
99 | switch (widthMode) {
100 | case MeasureSpec.EXACTLY:
101 | widthResult = widthSize;
102 | break;
103 | default:
104 | widthResult = Math.min(maxWidth, widthSize);
105 | // widthResult = widthSize;
106 | break;
107 | }
108 | switch (heightMode) {
109 | case MeasureSpec.EXACTLY:
110 | heightResult = heightSize;
111 | break;
112 | default:
113 | heightResult = maxHeight;
114 | break;
115 | }
116 | super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(heightResult, MeasureSpec.EXACTLY));
117 | setMeasuredDimension(widthResult, heightResult);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.activity;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.AdapterView;
8 | import android.widget.ArrayAdapter;
9 | import android.widget.ListView;
10 |
11 | import com.xk.customview.R;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Collections;
15 | import java.util.Comparator;
16 | import java.util.HashMap;
17 | import java.util.Set;
18 |
19 | public class MainActivity extends ViewBaseActivity {
20 | private static final String TAG = "MainActivity";
21 | private ArrayList datas;
22 | private HashMap> stringClassHashMap;
23 |
24 | @Override
25 | public void onCreate(Bundle savedInstanceState) {
26 | super.onCreate(savedInstanceState);
27 | setContentView(R.layout.activity_main);
28 | ListView listView = (ListView) findViewById(R.id.list);
29 |
30 | datas = new ArrayList<>();
31 | stringClassHashMap = new HashMap<>();
32 | stringClassHashMap.put("0.Demo", DemoActivityView.class);
33 | stringClassHashMap.put("1.叶子进度条", LeafProgressActivityView.class);
34 | stringClassHashMap.put("2.弹性球", MagicCircleActivityView.class);
35 | stringClassHashMap.put("3.蜘蛛网(游戏属性图)", SpiderActivityView.class);
36 | stringClassHashMap.put("4.贝塞尔曲线", QQBubbleActivityView.class);
37 | stringClassHashMap.put("5.刻度表", InstrumentActivityView.class);
38 | stringClassHashMap.put("6.圆->心", CircleAndHeartActivityView.class);
39 | stringClassHashMap.put("7.旋转箭头", RoationActivityView.class);
40 | stringClassHashMap.put("8.搜索放大镜", SearchActivityView.class);
41 | stringClassHashMap.put("9.模仿可缩放的imageview(鸿洋)", ZoomImageViewPagerActivityView.class);
42 | stringClassHashMap.put("10.模仿折叠的layout(鸿洋)", FoldingLayoutActivityView.class);
43 | stringClassHashMap.put("11.事件分发", EventDispatchActivityView.class);
44 | stringClassHashMap.put("12.3D画廊", GalleryActivityView.class);
45 | stringClassHashMap.put("13.仿华为应用商店 自动换行标签", HotLabelActivityView.class);
46 | stringClassHashMap.put("14.Drawable实现滚动颜色变化", RevealDrawableActivityView.class);
47 | stringClassHashMap.put("15.策略模式属性动画", StrategyAnimationActivity.class);
48 | stringClassHashMap.put("16.动画框架", AnimatorFrameActivity.class);
49 | stringClassHashMap.put("17.behavior", Behavior1Activity.class);
50 | stringClassHashMap.put("18.侧拉菜单动画", MenuAnimatorActivity.class);
51 | stringClassHashMap.put("19.RecyclerView", RecyclerViewActivity.class);
52 | stringClassHashMap.put("20.流式布局", FlowLayoutManagerActivity.class);
53 | Set strings = stringClassHashMap.keySet();
54 |
55 | for (String string : strings) {
56 | datas.add(string);
57 | }
58 | Collections.sort(datas, new Comparator() {
59 | @Override
60 | public int compare(String o1, String o2) {
61 | int o11 = getIndex(o1);
62 | int o22 = getIndex(o2);
63 | return -(o11 - o22);
64 | }
65 | });
66 | listView.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, datas));
67 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
68 | @Override
69 | public void onItemClick(AdapterView> parent, View view, int position, long id) {
70 | itemclick(parent, view, position, id);
71 | }
72 | });
73 | }
74 |
75 | public void itemclick(AdapterView> parent, View view, int position, long id) {
76 | Class extends Activity> aClass = stringClassHashMap.get(datas.get(position));
77 | Intent intent = new Intent(MainActivity.this, aClass);
78 | intent.putExtra("title", datas.get(position));
79 | startActivity(intent);
80 |
81 | }
82 |
83 | /**
84 | * 获取字符串的index
85 | *
86 | * @param o1
87 | * @return
88 | */
89 | private int getIndex(String o1) {
90 | String index = "";
91 | for (char c : o1.toCharArray()) {
92 | if (c <= '9' && c >= '0') {
93 | index += c;
94 | } else {
95 | break;
96 | }
97 | }
98 | return Integer.parseInt(index);
99 | }
100 |
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/WaveView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Canvas;
8 | import android.graphics.Color;
9 | import android.graphics.Matrix;
10 | import android.graphics.Paint;
11 | import android.graphics.Path;
12 | import android.graphics.PathMeasure;
13 | import android.support.annotation.Nullable;
14 | import android.util.AttributeSet;
15 | import android.util.Log;
16 | import android.view.View;
17 | import android.view.animation.LinearInterpolator;
18 |
19 | import com.xk.customview.R;
20 |
21 | public class WaveView extends View {
22 |
23 | private static final int INT_WAVE_LENGTH = 200;
24 | private static final String TAG = "WaveView";
25 |
26 | private Path mPath;
27 | private Paint mPaint;
28 |
29 | private int mDeltaX;
30 | private Bitmap mBitMap;
31 | private PathMeasure mPathMeasure;
32 | private float[] pos;
33 | private float[] tan;
34 | private Matrix mMatrix;
35 | private float faction;
36 |
37 | public WaveView(Context context) {
38 | super(context);
39 |
40 | }
41 |
42 | public WaveView(Context context, @Nullable AttributeSet attrs) {
43 | super(context, attrs);
44 | init();
45 |
46 | }
47 |
48 | private void init() {
49 | mPaint = new Paint();
50 | mPaint.setColor(Color.RED);
51 | mPaint.setStyle(Paint.Style.STROKE);
52 |
53 | mPath = new Path();
54 | pos = new float[2];
55 | tan = new float[2];
56 | mMatrix = new Matrix();
57 |
58 | BitmapFactory.Options options = new BitmapFactory.Options();
59 | options.inSampleSize = 2;
60 | mBitMap = BitmapFactory.decodeResource(getResources(),R.drawable.timg,options);
61 | }
62 |
63 | @Override
64 | protected void onDraw(Canvas canvas) {
65 | super.onDraw(canvas);
66 | mPath.reset();
67 | int orginY = 800;
68 | int halfWaveLength = INT_WAVE_LENGTH / 2;
69 | mPath.moveTo(-INT_WAVE_LENGTH + mDeltaX, orginY);
70 | for(int i = -INT_WAVE_LENGTH ; i < getWidth() ;
71 | i += INT_WAVE_LENGTH){
72 | mPath.rQuadTo(halfWaveLength/2,120,halfWaveLength,0);
73 | mPath.rQuadTo(halfWaveLength/2,-120,halfWaveLength,0);
74 |
75 | }
76 | mPath.lineTo(getWidth(),getHeight());
77 | mPath.lineTo(0,getHeight());
78 | mPath.close();
79 |
80 |
81 |
82 | canvas.drawPath(mPath,mPaint);
83 |
84 | mPathMeasure = new PathMeasure(mPath,false);
85 | float length = mPathMeasure.getLength();
86 | mMatrix.reset();
87 | boolean posTan = mPathMeasure.getPosTan(length*faction,pos,tan);
88 |
89 |
90 | canvas.drawCircle(tan[0],tan[1],20,mPaint);
91 | canvas.drawLine(tan[0],tan[1],pos[0],pos[1],mPaint);
92 |
93 | Log.i(TAG,"----------------------pos[0] = " + pos[0] + "pos[1] = " +pos[1]);
94 | Log.i(TAG,"----------------------tan[0] = " + tan[0] + "tan[1] = " +tan[1]);
95 | if(posTan){
96 | // 方案一 :自己计算
97 | // 将tan值通过反正切函数得到对应的弧度,在转化成对应的角度度数
98 | /*float degrees = (float) (Math.atan2(tan[1],tan[0])*180f / Math.PI);
99 | mMatrix.postRotate(degrees, mBitMap.getWidth()/2, mBitMap.getHeight() / 2);
100 | mMatrix.postTranslate(pos[0]- mBitMap.getWidth() / 2,pos[1] - mBitMap.getHeight());
101 | canvas.drawBitmap(mBitMap,mMatrix,mPaint);*/
102 |
103 | // 方案二 :直接使用API
104 | //直接帮你算了角度并且帮你进行了旋转
105 | mPathMeasure.getMatrix(length*faction, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);
106 | mMatrix.preTranslate(- mBitMap.getWidth() / 2, - mBitMap.getHeight());
107 | canvas.drawBitmap(mBitMap,mMatrix,mPaint);
108 | }
109 |
110 | //
111 |
112 | }
113 |
114 | public void startAnimation(){
115 | // ValueAnimator anim = ValueAnimator.ofInt(0,INT_WAVE_LENGTH);
116 | // anim.setDuration(1000);
117 | // anim.setInterpolator(new LinearInterpolator());
118 | // anim.setRepeatCount(ValueAnimator.INFINITE);
119 | // anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
120 | // @Override
121 | // public void onAnimationUpdate(ValueAnimator animation) {
122 | // mDeltaX = (int) animation.getAnimatedValue();
123 | // postInvalidate();
124 | // }
125 | // });
126 | // anim.start();
127 |
128 | ValueAnimator animator = ValueAnimator.ofFloat(0,1);
129 | animator.setDuration(10000);
130 | animator.setInterpolator(new LinearInterpolator());
131 | animator.setRepeatCount(ValueAnimator.INFINITE);
132 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
133 | @Override
134 | public void onAnimationUpdate(ValueAnimator animation) {
135 | faction = (float) animation.getAnimatedValue();
136 | Log.i(TAG,"----------------------faction = " + faction);
137 | postInvalidate();
138 | }
139 | });
140 | animator.start();
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/QQBubbleView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.PointF;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 |
14 | import static android.R.attr.max;
15 | import static android.R.attr.radius;
16 | import static android.R.attr.y;
17 |
18 | /**
19 | * QQ消息气泡
20 | * Created by X.Sation on 2017/2/19.
21 | */
22 |
23 | public class QQBubbleView extends View {
24 |
25 | //注:old指的是原地的
26 |
27 | private static final String TAG = "QQBubbleView";
28 | private PointF control;
29 | private PointF oldTop;
30 | private PointF oldBottom;
31 | private PointF newTop;
32 | private PointF newBottom;
33 | private int radius;
34 | private Paint paint;
35 | private boolean isBreak = false;
36 | private boolean isShow = true;
37 |
38 | /**
39 | * 当前手指的位置与中心连线的角度
40 | */
41 | private float direction = 0;
42 | private double roation;
43 |
44 |
45 | public QQBubbleView(Context context, AttributeSet attrs) {
46 | super(context, attrs);
47 | init();
48 | }
49 |
50 | private void init() {
51 | paint = new Paint();
52 | paint.setAntiAlias(true);
53 | paint.setStrokeWidth(2);
54 | paint.setColor(Color.RED);
55 | control = new PointF(0, 0);
56 | radius = 30;
57 | oldTop = new PointF(0, -radius);
58 | oldBottom = new PointF(0, radius);
59 | newTop = new PointF(0, -radius);
60 | newBottom = new PointF(0, radius);
61 | }
62 |
63 | @Override
64 | protected void onDraw(Canvas canvas) {
65 | if (isShow) {
66 |
67 |
68 | canvas.save();
69 | canvas.translate(getWidth() / 2, getHeight() / 2);
70 | canvas.rotate((float) ((float) roation / Math.PI * 180));
71 |
72 | control.x = (newTop.x - oldTop.x) / 2;
73 | paint.setStyle(Paint.Style.FILL);
74 | Path path = new Path();
75 | path.moveTo(oldTop.x, oldTop.y);
76 | path.quadTo(control.x, control.y, newTop.x, newTop.y);
77 | path.lineTo(newBottom.x, newBottom.y);
78 | path.quadTo(control.x, control.y, oldBottom.x, oldBottom.y);
79 | path.close();
80 |
81 | path.setFillType(Path.FillType.WINDING);
82 |
83 | if (!isBreak) {
84 | canvas.drawPath(path, paint);
85 | }
86 |
87 | paint.setStyle(Paint.Style.FILL);
88 | if (!isBreak) {
89 | drawOldCircle(canvas);
90 | }
91 | drawNewCircle(canvas);
92 | canvas.restore();
93 |
94 | }
95 | // drawCoordinate(canvas);
96 |
97 | }
98 |
99 | private void drawNewCircle(Canvas canvas) {
100 | canvas.drawCircle(newTop.x, 0, radius, paint);
101 | }
102 |
103 | private void drawCoordinate(Canvas canvas) {
104 | canvas.save();
105 | canvas.translate(getWidth() / 2, getHeight() / 2);
106 | // canvas.rotate((float) ((float) roation / Math.PI * 180));
107 | canvas.drawLine(0, -getHeight() / 2 + 5, 0, getHeight() / 2 - 5, paint);
108 | canvas.drawLine(-getWidth() / 2 + 5, 0, getWidth() / 2 - 5, 0, paint);
109 | canvas.restore();
110 | }
111 |
112 | private void drawOldCircle(Canvas canvas) {
113 | canvas.drawCircle(oldTop.x, 0, oldBottom.y, paint);
114 | }
115 |
116 | /**
117 | * 拉断时的距离
118 | */
119 | private int maxDistance = 500;
120 |
121 | @Override
122 | public boolean onTouchEvent(MotionEvent event) {
123 | float x = (event.getX() - getWidth() / 2);
124 | float y = (event.getY() - getHeight() / 2);
125 | roation = Math.atan(y / x);
126 |
127 | x = (float) (x / Math.cos(roation));
128 | y = 0;
129 |
130 | newTop.x = x;
131 | newBottom.x = x;
132 | float distance = (float) Math.sqrt((x - oldTop.x) * (x - oldTop.x) + (y - oldTop.y) * (y - oldTop.y));
133 |
134 | switch (event.getAction()) {
135 | case MotionEvent.ACTION_DOWN:
136 | if (Math.abs(x) < radius && Math.abs(y) < radius) {
137 | return true;
138 | } else {
139 | return false;
140 | }
141 | case MotionEvent.ACTION_MOVE:
142 |
143 |
144 | if (distance < maxDistance) {
145 | oldTop.y = -(2 * newTop.y / 4 / Math.abs(maxDistance) * Math.abs(distance) - newTop.y);
146 | oldBottom.y = (2 * newTop.y / 4 / Math.abs(maxDistance) * Math.abs(distance) - newTop.y);
147 | } else {
148 | isBreak = true;
149 | }
150 |
151 | break;
152 | case MotionEvent.ACTION_UP:
153 | if (isBreak) {
154 | if (distance < maxDistance) {
155 | newTop.set(0, -radius);
156 | newBottom.set(0, radius);
157 | isBreak=false;
158 | } else {
159 | isShow = false;
160 | }
161 | } else {
162 | newTop.set(0, -radius);
163 | newBottom.set(0, radius);
164 | }
165 |
166 | break;
167 | }
168 | invalidate();
169 | return super.onTouchEvent(event);
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_behavior.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
19 |
20 |
23 |
24 |
27 |
28 |
31 |
32 |
35 |
36 |
39 |
40 |
43 |
44 |
47 |
48 |
51 |
52 |
55 |
56 |
59 |
60 |
63 |
64 |
67 |
68 |
71 |
72 |
75 |
76 |
79 |
80 |
83 |
84 |
87 |
88 |
91 |
92 |
95 |
96 |
99 |
100 |
103 |
104 |
107 |
108 |
111 |
112 |
115 |
116 |
119 |
120 |
123 |
124 |
127 |
128 |
131 |
132 |
135 |
136 |
139 |
140 |
143 |
144 |
147 |
148 |
151 |
152 |
153 |
154 |
160 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/CircleAndHeartView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PointF;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 |
15 | /**
16 | * 从圆到心
17 | * 来自https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B06%5DPath_Bezier.md
18 | * Created by xuekai on 2017/2/17.
19 | */
20 |
21 | public class CircleAndHeartView extends View {
22 |
23 | private PointF dataA;
24 | private PointF dataB;
25 | private PointF dataC;
26 | private PointF dataD;
27 | private PointF controlA;
28 | private PointF controlB;
29 | private PointF controlC;
30 | private PointF controlD;
31 | private PointF controlE;
32 | private PointF controlF;
33 | private PointF controlG;
34 | private PointF controlH;
35 | private int width;
36 | private int height;
37 | private Paint paint;
38 | private int raduis=200;
39 | //datac的偏移量(变心的时候)
40 | private float dcOffset;
41 | private float daOffset;
42 |
43 | private final float C = 0.551915024494f; // 一个常量,用来计算绘制圆形贝塞尔曲线控制点的位置
44 |
45 | private float mDifference = raduis * C; // 圆形的控制点与数据点的差值
46 |
47 |
48 | public CircleAndHeartView(Context context) {
49 | this(context, null);
50 | }
51 |
52 | public CircleAndHeartView(Context context, AttributeSet attrs) {
53 | super(context, attrs);
54 | init();
55 | }
56 |
57 | private void init() {
58 | dataA = new PointF();
59 | dataB = new PointF();
60 | dataC = new PointF();
61 | dataD = new PointF();
62 | controlA = new PointF();
63 | controlB = new PointF();
64 | controlC = new PointF();
65 | controlD = new PointF();
66 | controlE = new PointF();
67 | controlF = new PointF();
68 | controlG = new PointF();
69 | controlH = new PointF();
70 | paint = new Paint();
71 | paint.setStrokeWidth(2);
72 | paint.setAntiAlias(true);
73 | paint.setStyle(Paint.Style.STROKE);
74 | }
75 |
76 |
77 | @Override
78 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
79 | dataB.set(raduis, 0);
80 | dataD.set(-raduis, 0);
81 |
82 |
83 | controlA.set(0 + mDifference, raduis);
84 | controlB.set(raduis, 0 + mDifference);
85 | controlC.set(raduis, 0 - mDifference);
86 | controlD.set(0 + mDifference, -raduis);
87 |
88 |
89 | controlE.set(0 - mDifference, -raduis);
90 | controlF.set(-raduis, 0 - mDifference);
91 | controlG.set(-raduis, 0 + mDifference);
92 | controlH.set(0 - mDifference, raduis);
93 |
94 |
95 | width = w;
96 | height = h;
97 | }
98 |
99 | @Override
100 | protected void onDraw(Canvas canvas) {
101 | dataC.set(0, -raduis+dcOffset);
102 | dataA.set(0, raduis+daOffset);
103 |
104 |
105 | drawCoordinate(canvas);
106 | canvas.save();
107 | canvas.translate(width / 2, height / 2);
108 |
109 | Path path = new Path();
110 | path.moveTo(dataA.x, dataA.y);
111 |
112 |
113 | path.cubicTo(controlA.x, controlA.y, controlB.x, controlB.y, dataB.x, dataB.y);
114 | path.cubicTo(controlC.x, controlC.y, controlD.x, controlD.y, dataC.x, dataC.y);
115 | path.cubicTo(controlE.x, controlE.y, controlF.x, controlF.y, dataD.x, dataD.y);
116 | path.cubicTo(controlG.x, controlG.y, controlH.x, controlH.y, dataA.x, dataA.y);
117 |
118 | canvas.drawPath(path, paint);
119 | paint.setColor(Color.BLUE);
120 |
121 |
122 | paint.setTextSize(30);
123 | canvas.drawText("a",dataA.x,dataA.y,paint);
124 | canvas.drawText("b",dataB.x,dataB.y,paint);
125 | canvas.drawText("c",dataC.x,dataC.y,paint);
126 | canvas.drawText("d",dataD.x,dataD.y,paint);
127 | canvas.drawText("ka",controlA.x,controlA.y,paint);
128 | canvas.drawText("kb",controlB.x,controlB.y,paint);
129 | canvas.drawText("kc",controlC.x,controlC.y,paint);
130 | canvas.drawText("kd",controlD.x,controlD.y,paint);
131 | canvas.drawText("ke", controlE.x, controlE.y, paint);
132 | canvas.drawText("kf", controlF.x, controlF.y, paint);
133 | canvas.drawText("kg", controlG.x, controlG.y, paint);
134 | canvas.drawText("kh", controlH.x, controlH.y, paint);
135 |
136 | canvas.restore();
137 | }
138 |
139 | private void drawCoordinate(Canvas canvas) {
140 | canvas.save();
141 | paint.setColor(0xff000000);
142 | canvas.translate(getWidth() / 2, getHeight() / 2);
143 | canvas.drawLine(-getWidth() / 2 + 5, 0, getWidth() / 2 - 5, 0, paint);
144 | canvas.drawLine(0, -getHeight() / 2 + 5, 0, getHeight() / 2 - 5, paint);
145 | paint.setColor(Color.RED); // 绘制合并后的路径
146 | canvas.restore();
147 | }
148 |
149 | public void startAnimation(){
150 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
151 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
152 | @Override
153 | public void onAnimationUpdate(ValueAnimator animation) {
154 | float value = (float) animation.getAnimatedValue();
155 | //这里自己调整,通过调整各个data点和control点,可以使心更好看,只是为了学习,所以我就随便动了两个点
156 | dcOffset=value*raduis/2;
157 | daOffset=value*raduis/2;
158 | invalidate();
159 | }
160 | });
161 | valueAnimator.setDuration(2000);
162 | valueAnimator.start();
163 | }
164 |
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/FoldingLayout.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.LinearGradient;
9 | import android.graphics.Matrix;
10 | import android.graphics.Paint;
11 | import android.graphics.RectF;
12 | import android.graphics.Shader;
13 | import android.graphics.drawable.BitmapDrawable;
14 | import android.graphics.drawable.Drawable;
15 | import android.util.AttributeSet;
16 | import android.util.Log;
17 | import android.view.View;
18 |
19 | import com.xk.customview.R;
20 |
21 |
22 | /**
23 | * 这个View坑死我了,deltaY = (float) (currentWidth / FOLD_COUNT / Math.tan(foldAngle))/3;
24 | * 现在我都不知道为啥要除以3,为啥不除以三就会不显示
25 | *
26 | * 注意:像这种使用矩阵变形,并且结合clipRect,需注意以上
27 | * Created by xuekai on 2017/2/23.
28 | */
29 |
30 | public class FoldingLayout extends View {
31 | //real表示完全展开的宽度 current表示折叠后的宽度
32 | private float foldAngle = (float) (Math.PI / 2);
33 | private float realWidth;
34 | private float realHeight;
35 | private float currentWidth;
36 | private float currentHeight;
37 |
38 | private float bitmapWidth;
39 | private float bitmapHeight;
40 |
41 | private float[] src = new float[8];
42 | private float[] dst = new float[8];
43 |
44 | //折叠后y的偏移量
45 | private float deltaY;
46 |
47 | //左半边的阴影画笔
48 | private Paint allShaderpaint;
49 | //右半边的阴影画笔
50 | private Paint halfShaderpaint;
51 |
52 | private int FOLD_COUNT = 6;
53 | private Matrix[] matrices = new Matrix[FOLD_COUNT];
54 | private Bitmap bitmap;
55 |
56 |
57 | public FoldingLayout(Context context, AttributeSet attrs) {
58 | super(context, attrs);
59 | init();
60 | }
61 |
62 |
63 | private void init() {
64 | allShaderpaint = new Paint();
65 | allShaderpaint.setColor(0xff000000);
66 |
67 | halfShaderpaint = new Paint();
68 | halfShaderpaint.setStyle(Paint.Style.FILL);
69 |
70 | }
71 |
72 | @Override
73 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
74 |
75 | realHeight = h;
76 | realWidth = w;
77 |
78 | bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.dada);
79 | bitmapWidth = bitmap.getWidth();
80 | bitmapHeight = bitmap.getHeight();
81 |
82 |
83 | for (int i = 0; i < FOLD_COUNT; i++) {
84 | matrices[i] = new Matrix();
85 | }
86 | }
87 |
88 | @Override
89 | protected void onDraw(Canvas canvas) {
90 | initMatrix();
91 |
92 | for (int i = 0; i < FOLD_COUNT; i++) {
93 | canvas.save();
94 | canvas.concat(matrices[i]);
95 |
96 | canvas.clipRect(bitmapWidth / FOLD_COUNT * i, 0, bitmapWidth / FOLD_COUNT * (i + 1),
97 | getHeight());
98 | canvas.drawBitmap(bitmap, 0, 0, null);
99 |
100 |
101 | canvas.translate(bitmapWidth / FOLD_COUNT * i, 0);
102 |
103 |
104 | double value = 0.9 - 1.8 / Math.PI * foldAngle;
105 | double value1 = bitmapWidth / FOLD_COUNT -2*bitmapWidth/FOLD_COUNT/Math.PI*foldAngle;
106 |
107 | allShaderpaint.setAlpha((int) (255 * 0.8f * value));
108 | halfShaderpaint.setAlpha((int) (255 * 0.8f * value));
109 | if (i % 2 == 0) {
110 | //绘制左半边阴影
111 | canvas.drawRect(0, 0, bitmapWidth / FOLD_COUNT, bitmapHeight, allShaderpaint);
112 | } else {
113 | // //绘制右半边阴影
114 | LinearGradient linearGradient = new LinearGradient(0, 0,0.7f ,0,
115 | Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP);
116 | halfShaderpaint.setShader(linearGradient);
117 | Matrix matrix = new Matrix();
118 | Log.e(TAG,"onDraw11111"+i+" "+value1);
119 | matrix.postScale((float) value1,1);
120 | linearGradient.setLocalMatrix(matrix);
121 | canvas.drawRect(0, 0, bitmapWidth / FOLD_COUNT, bitmapHeight, halfShaderpaint);
122 | }
123 | canvas.restore();
124 | allShaderpaint.setAlpha(00);
125 |
126 | }
127 | change();
128 |
129 | }
130 |
131 | private static final String TAG = "FoldingLayout";
132 |
133 | private void initMatrix() {
134 | currentWidth = (float) (bitmapWidth * Math.sin(foldAngle));
135 | currentHeight = bitmapHeight;
136 |
137 | //这里比较坑爹
138 | deltaY = (float) (currentWidth / FOLD_COUNT / Math.tan(foldAngle)) / 3;
139 |
140 | for (int i = 0; i < FOLD_COUNT; i++) {
141 | matrices[i].reset();
142 | src[0] = i * bitmapWidth / FOLD_COUNT;
143 | src[1] = 0;
144 | src[2] = src[0] + bitmapWidth / FOLD_COUNT;
145 | src[3] = 0;
146 | src[4] = src[0];
147 | src[5] = bitmapHeight;
148 | src[6] = src[2];
149 | src[7] = src[5];
150 |
151 |
152 | boolean isEven = i % 2 == 0;
153 |
154 | dst[0] = i * currentWidth / FOLD_COUNT;
155 | dst[1] = isEven ? 0 : deltaY;
156 | dst[2] = dst[0] + currentWidth / FOLD_COUNT;
157 | dst[3] = isEven ? deltaY : 0;
158 | dst[4] = dst[0];
159 | dst[5] = isEven ? currentHeight : currentHeight - deltaY;
160 | dst[6] = dst[2];
161 | dst[7] = isEven ? currentHeight - deltaY : currentHeight;
162 |
163 | for (int y = 0; y < 8; y++) {
164 | dst[y] = Math.round(dst[y]);
165 | }
166 |
167 |
168 | matrices[i].setPolyToPoly(src, 0, dst, 0, 4);
169 |
170 | }
171 |
172 |
173 | }
174 |
175 |
176 | public void change() {
177 | foldAngle = (float) (foldAngle - 0.01);
178 | if (foldAngle <= 0) foldAngle = (float) (Math.PI / 2);
179 | invalidate();
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/gallery/GalleryView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.gallery;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.util.AttributeSet;
8 | import android.util.Log;
9 | import android.util.SparseArray;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 | import android.view.animation.LinearInterpolator;
13 | import android.widget.Scroller;
14 |
15 | /**
16 | * Created by xuekai on 2017/2/27.
17 | */
18 |
19 | public class GalleryView extends RecyclerView {
20 | private static final String TAG = "GalleryView";
21 | private Scroller mScroller;
22 |
23 | private boolean isLeft;
24 |
25 | private GalleryItem galleryRightItem1;
26 | private GalleryItem galleryRightItem2;
27 | private GalleryItem galleryRightItem3;
28 | //动画事件
29 | private int duration = 300;
30 | //间隔的事件
31 | private int interval = 1000;
32 | private GalleryAdapter galleryAdapter;
33 |
34 | public GalleryView(Context context, @Nullable AttributeSet attrs) {
35 | super(context, attrs);
36 | init();
37 | }
38 |
39 | private void init() {
40 | mScroller = new Scroller(getContext(), new LinearInterpolator());
41 | setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
42 | galleryAdapter = new GalleryAdapter(getContext(), null);
43 | setAdapter(galleryAdapter);
44 | }
45 |
46 | public void setDatas(SparseArray datas) {
47 | galleryAdapter.setDatas(datas);
48 | }
49 |
50 | /**
51 | * 下一张图片
52 | */
53 | public void next() {
54 | if (!mScroller.isFinished()) return;
55 |
56 | isLeft = false;
57 | isPrepare = false;
58 | //runnable,让即将出现的那个一个item提前变成梯形
59 | smoothScroll(getWidth() / 3, new Runnable() {
60 | @Override
61 | public void run() {
62 | galleryRightItem1 = (GalleryItem) getChildAt(1);
63 | galleryRightItem2 = (GalleryItem) getChildAt(2);
64 | galleryRightItem3 = (GalleryItem) getChildAt(3);
65 | galleryRightItem3.setMatrix(GalleryItem.STATE_RIGHT);
66 | isPrepare = true;
67 | }
68 | }, 10);
69 | }
70 |
71 | @Override
72 | public void onChildAttachedToWindow(View child) {
73 | if (getChildAt(0) == child) {
74 | ((GalleryItem) child).setOriginalState(GalleryItem.STATE_LEFT);
75 | }
76 | if (getChildAt(1) == child) {
77 | ((GalleryItem) child).setOriginalState(GalleryItem.STATE_MIDDLE);
78 | }
79 | if (getChildAt(2) == child) {
80 | ((GalleryItem) child).setOriginalState(GalleryItem.STATE_RIGHT);
81 | }
82 | }
83 |
84 | private GalleryItem galleryLeftItem0;
85 | private GalleryItem galleryLeftItem1;
86 | private GalleryItem galleryLeftItem2;
87 | private GalleryItem galleryLeftItem3;
88 |
89 |
90 | /**
91 | * 上一张图片
92 | */
93 | public void last() {
94 | if (!mScroller.isFinished()) return;
95 | isLeft = true;
96 | isPrepare = false;
97 |
98 | smoothScroll(-getWidth() / 3, new Runnable() {
99 | @Override
100 | public void run() {
101 | //从left变成middle
102 | galleryLeftItem0 = (GalleryItem) getChildAt(0);
103 | galleryLeftItem1 = (GalleryItem) getChildAt(1);
104 | galleryLeftItem2 = (GalleryItem) getChildAt(2);
105 | galleryLeftItem3 = (GalleryItem) getChildAt(3);
106 | galleryLeftItem0.setMatrix(GalleryItem.STATE_LEFT);
107 | galleryLeftItem3.setMatrix(GalleryItem.STATE_RIGHT);
108 | isPrepare = true;
109 | }
110 | }, 10);
111 |
112 | }
113 |
114 | boolean isPrepare = false;
115 |
116 | @Override
117 | public void computeScroll() {
118 | int lastX = 0;
119 | if (isFirst) {
120 | isFirst = false;
121 | } else {
122 | lastX = mScroller.getCurrX();
123 | }
124 | if (mScroller.computeScrollOffset()) {
125 | scrollBy(mScroller.getCurrX() - lastX, 0);
126 | if (isPrepare) {
127 | onScroll(mScroller.timePassed() * 1f / mScroller.getDuration());
128 | }
129 | invalidate();
130 | }
131 | }
132 |
133 | private boolean isFirst;
134 |
135 |
136 | public void smoothScroll(int dx, Runnable runnable, int time) {
137 | mScroller.startScroll(getScrollX(), getScrollY(), dx, 0, duration);
138 | isFirst = true;
139 | invalidate();
140 | if (time == 0) {
141 | runnable.run();
142 | } else {
143 | postDelayed(runnable, time);
144 | }
145 | }
146 |
147 | @Override
148 | public boolean onTouchEvent(MotionEvent e) {
149 | return false;
150 | }
151 |
152 | private void onScroll(float progress) {
153 | if (progress > 1) {
154 | progress = 1;
155 | }
156 | if (isLeft) {
157 | //向左滑动
158 | if (galleryLeftItem1 != null && galleryLeftItem2 != null) {
159 | galleryLeftItem1.change(progress, GalleryItem.STATE_LEFT, GalleryItem.STATE_MIDDLE);
160 | galleryLeftItem2.change(progress, GalleryItem.STATE_MIDDLE, GalleryItem.STATE_RIGHT);
161 | }
162 |
163 | } else {
164 | //向右滑动
165 | if (galleryRightItem1 != null && galleryRightItem2 != null) {
166 | galleryRightItem1.change(progress, GalleryItem.STATE_MIDDLE, GalleryItem.STATE_LEFT);
167 | galleryRightItem2.change(progress, GalleryItem.STATE_RIGHT, GalleryItem.STATE_MIDDLE);
168 |
169 | }
170 |
171 | }
172 | }
173 |
174 | @Override
175 | public void setAdapter(Adapter adapter) {
176 | super.setAdapter(adapter);
177 | scrollToPosition(Integer.MAX_VALUE / 2);
178 | }
179 |
180 | private boolean isStop = true;
181 |
182 | public void stop() {
183 | isStop = true;
184 | }
185 |
186 | public void start() {
187 | isStop = false;
188 | play();
189 | }
190 |
191 | /**
192 | * 设置动画执行的时间
193 | * @param duration
194 | */
195 | public void setDuration(int duration){
196 | this.duration=duration;
197 | } /**
198 | * 设置动画执行的时间
199 | * @param interval
200 | */
201 | public void setInterval(int interval){
202 | this.interval=interval;
203 | }
204 | private void play() {
205 | postDelayed(new Runnable() {
206 | @Override
207 | public void run() {
208 | if (!isStop) {
209 | next();
210 | play();
211 | }
212 | }
213 | }, interval + duration);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/gallery/GalleryItem.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.gallery;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Matrix;
6 | import android.graphics.Paint;
7 | import android.graphics.RectF;
8 | import android.graphics.drawable.Drawable;
9 | import android.support.annotation.IntDef;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.widget.ImageView;
13 |
14 |
15 | /**
16 | * Created by xuekai on 2017/2/27.
17 | */
18 |
19 | public class GalleryItem extends ImageView {
20 | private static final String TAG = "GalleryItem";
21 |
22 | public static final int STATE_LEFT = 1;
23 | public static final int STATE_RIGHT = 2;
24 | public static final int STATE_MIDDLE = 3;
25 | private int width;
26 | private int height;
27 |
28 | //原始矩形
29 | private RectF originalRect;
30 |
31 | private
32 | @MatrixState
33 | int currentState;
34 |
35 | @IntDef({STATE_LEFT, STATE_RIGHT, STATE_MIDDLE})
36 | public @interface MatrixState {
37 | }
38 |
39 | private Drawable drawable;
40 | private Matrix matrix;
41 | private float[] src;
42 | private float[] dst;
43 | //上下缩回去的值与总高度的比例
44 | private float scaleRatio = 0.2f;
45 | private int intrinsicWidth;
46 | private int intrinsicHeight;
47 |
48 | private boolean sizeChange = false;
49 |
50 | private @MatrixState int originalState;
51 |
52 | public void setOriginalState(int originalState) {
53 | this.originalState = originalState;
54 | }
55 |
56 | public GalleryItem(Context context, AttributeSet attrs) {
57 | super(context, attrs);
58 | init();
59 | }
60 |
61 | private void init() {
62 | setScaleType(ScaleType.MATRIX);
63 | matrix = new Matrix();
64 | src = new float[8];
65 | dst = new float[8];
66 | }
67 |
68 |
69 | @Override
70 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
71 | sizeChange = true;
72 | width = w;
73 | height = h;
74 | originalRect = new RectF();
75 | drawable = getDrawable();
76 | maunalFitXY();
77 |
78 | setMatrix(originalState);
79 | }
80 |
81 | private void maunalFitXY() {
82 | matrix.reset();
83 | drawable = getDrawable();
84 | intrinsicWidth = drawable.getIntrinsicWidth();
85 | intrinsicHeight = drawable.getIntrinsicHeight();
86 | src[0] = 0;
87 | src[1] = 0;
88 | src[2] = intrinsicWidth;
89 | src[3] = 0;
90 | src[4] = 0;
91 | src[5] = intrinsicHeight;
92 | src[6] = intrinsicWidth;
93 | src[7] = intrinsicHeight;
94 |
95 | dst[0] = 0;
96 | dst[1] = 0;
97 | dst[2] = width - getPaddingLeft() - getPaddingRight();
98 | dst[3] = 0;
99 | dst[4] = 0;
100 | dst[5] = height - getPaddingTop() - getPaddingBottom();
101 | dst[6] = width - getPaddingLeft() - getPaddingRight();
102 | dst[7] = height - getPaddingTop() - getPaddingBottom();
103 |
104 | matrix.setPolyToPoly(src, 0, dst, 0, 4);
105 | setImageMatrix(matrix);
106 |
107 | originalRect.set(0, 0, intrinsicWidth, intrinsicHeight);
108 | matrix.mapRect(originalRect);
109 |
110 | }
111 |
112 | @Override
113 | protected void onAttachedToWindow() {
114 | super.onAttachedToWindow();
115 | if (sizeChange) {
116 | maunalFitXY();
117 | }
118 |
119 | }
120 |
121 | private void initDst() {
122 | dst[0] = 0;
123 | dst[1] = 0;
124 | dst[2] = width - getPaddingLeft() - getPaddingRight();
125 | dst[3] = 0;
126 | dst[4] = 0;
127 | dst[5] = height - getPaddingTop() - getPaddingBottom();
128 | dst[6] = width - getPaddingLeft() - getPaddingRight();
129 | dst[7] = height - getPaddingTop() - getPaddingBottom();
130 | }
131 |
132 |
133 | /**
134 | * 设置展示样式
135 | */
136 | public void setMatrix(@MatrixState int state) {
137 | initDst();
138 | matrix.reset();
139 |
140 | switch (state) {
141 | case STATE_LEFT:
142 | dst[3] = dst[3] + intrinsicHeight * scaleRatio;
143 | dst[7] = dst[7] - intrinsicHeight * scaleRatio;
144 | break;
145 | case STATE_RIGHT:
146 | dst[1] = dst[1] + intrinsicHeight * scaleRatio;
147 | dst[5] = dst[5] - intrinsicHeight * scaleRatio;
148 | break;
149 | case STATE_MIDDLE:
150 | break;
151 | }
152 | matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
153 | setImageMatrix(matrix);
154 | }
155 |
156 |
157 | /**
158 | * 变形
159 | *
160 | * @param progress 根据这个参数,来控制形变(正数表示向右,负数表示向左)
161 | * @param fromState
162 | * @param toState
163 | */
164 | public void change(float progress, @MatrixState int fromState, @MatrixState int toState) {
165 | initDst();
166 | matrix.reset();
167 |
168 | switch (fromState) {
169 | case STATE_LEFT:
170 | if (toState == STATE_MIDDLE) {
171 | dst[3] = dst[3] + intrinsicHeight * scaleRatio * (1 - progress);
172 | dst[7] = dst[7] - intrinsicHeight * scaleRatio * (1 - progress);
173 | currentState = STATE_MIDDLE;
174 | } else {
175 | throw new IllegalStateException("只能从STATE_LEFT转换成STATE_MIDDLE");
176 | }
177 | break;
178 | case STATE_RIGHT:
179 | if (toState == STATE_MIDDLE) {
180 | dst[1] = dst[1] + intrinsicHeight * scaleRatio * (1 - progress);
181 | dst[5] = dst[5] - intrinsicHeight * scaleRatio * (1 - progress);
182 | currentState = STATE_MIDDLE;
183 |
184 | } else {
185 | throw new IllegalStateException("只能从STATE_RIGHT转换成STATE_MIDDLE");
186 | }
187 | break;
188 | case STATE_MIDDLE:
189 | if (toState == STATE_RIGHT) {
190 | dst[1] = dst[1] + intrinsicHeight * scaleRatio * progress;
191 | dst[5] = dst[5] - intrinsicHeight * scaleRatio * progress;
192 | currentState = STATE_RIGHT;
193 |
194 | } else if (toState == STATE_LEFT) {
195 | dst[3] = dst[3] + intrinsicHeight * scaleRatio * progress;
196 | dst[7] = dst[7] - intrinsicHeight * scaleRatio * progress;
197 | currentState = STATE_LEFT;
198 | } else {
199 | throw new IllegalStateException("不能从STATE_MIDDLE转换成STATE_LEFT");
200 |
201 | }
202 | break;
203 | }
204 | matrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
205 | setImageMatrix(matrix);
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
89 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/flowlayoutmanager/FlowLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.flowlayoutmanager;
2 |
3 | import android.graphics.Rect;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.util.Log;
6 | import android.util.SparseArray;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | /**
11 | * @author xuekai
12 | * @date 2019/4/26
13 | */
14 | public class FlowLayoutManager extends RecyclerView.LayoutManager {
15 | /**
16 | * 存放所有孩子的位置。认为第一个孩子所处的位置为0,0点
17 | */
18 | SparseArray frames = new SparseArray<>();
19 |
20 | /**
21 | */
22 | int scrollY = 0;
23 |
24 |
25 | //保存所有item偏移量信息
26 | // 所有数据高度和
27 | private int totalHeight = 0;
28 |
29 | /**
30 | * 定义一个全局的变量,用来存放当前recyclerview滚了多高了
31 | * 滑动偏移量
32 | * 如果是正的就是在向上滑,展现上面的view
33 | * 如果是负的向下
34 | */
35 | private int verticalScrollOffset = 0;
36 |
37 | @Override
38 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
39 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
40 | }
41 |
42 | // 在RecyclerView中,有两个缓存:Scrap和Recycle。Scrap中文就是废料的意思,Recycle对应是回收的意思。这两个缓存有什么作用呢?首先Scrap缓存是指里面缓存的View是接下来需要用到的,即里面的绑定的数据无需更改,可以直接拿来用的,是一个轻量级的缓存集合;而Recycle的缓存的View为里面的数据需要重新绑定,即需要通过Adapter重新绑定数据。关于这两个缓存的使用场景,下一节详细介绍。
43 | //
44 | // 当我们去获取一个新的View时,RecyclerView首先去检查Scrap缓存是否有对应的position的View,如果有,则直接拿出来可以直接用,不用去重新绑定数据;如果没有,则从Recycle缓存中取,并且会回调Adapter的onBindViewHolder方法(当然了,如果Recycle缓存为空,还会调用onCreateViewHolder方法),最后再将绑定好新数据的View返回。
45 |
46 |
47 | // 前面我们了解到,RecyclerView中有二级缓存,我们可以自己选择将View缓存到哪里。我们有两种选择的方式:Detach和Remove。Detach的View放在Scrap缓存中,Remove掉的View放在Recycle缓存中;那我们应该如何去选择呢?
48 | //
49 | // 在什么样的场景中使用Detach呢?主要是在我们的代码执行结束之前,我们需要反复去将View移除并且马上又要添加进去时,选择Datach方式,比如:当我们对View进行重新排序的时候,可以选择Detach,因为屏幕上显示的就是这些position对应的View,我们并不需要重新去绑定数据,这明显可以提高效率。使用Detach方式可以通过函数detachAndScrapView()实现。
50 | //
51 | // 而使用Remove的方式,是当View不在屏幕中有任何显示的时候,你需要将它Remove掉,以备后面循环利用。可以通过函数removeAndRecycleView()实现。
52 | @Override
53 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
54 | Log.i("FlowLayoutManager","onLayoutChildren-->");
55 | //如果没有item,直接返回
56 | if (getItemCount() <= 0) {
57 | return;
58 | }
59 | // 跳过preLayout,preLayout主要用于支持动画
60 | if (state.isPreLayout()) {
61 | return;
62 | }
63 | //onLayout之前,处理一下
64 | detachAndScrapAttachedViews(recycler);
65 | //可以理解为:准备摆放孩子时,当前光标所在的位置。0,0点在第一个孩子那里,而不是RecyclerView左上角
66 | int yOffset = 0, xOffset = 0;
67 | //一行中最高的那个孩子的高度
68 | int lineMaxHeight = 0;
69 | //遍历所有孩子。其实这里可以优化,只遍历可以显示在屏幕上的孩子
70 | for (int i = 0; i < getItemCount(); i++) {
71 | //通过回收池,通过position拿到一个view。其实就是从各种缓存池等地方或者通过onCreateViewHolder,然后通过getItemType
72 | //把position转成viewtype,从而拿到一个对应的view
73 | View child = recycler.getViewForPosition(i);
74 | //要布局view,就需要知道他的宽高,而要测量,肯定要依托于父亲的参数,所以先addView
75 | addView(child);
76 | //利用layoutmanager提供的测量方法测量。后两个参数表示used的宽度,也就是测量的时候,需要减去这些宽度,作为父亲的可用宽度进行测量
77 | measureChildWithMargins(child, 0, 0);
78 | //测量结束,拿孩子的宽高,其实可以直接通过child.getMeasureWidth的,但是child可能会设置一些边距之类的,所以通过下面
79 | //方法,这样获取到的才是孩子的宽高,所以孩子的宽高为真实宽高加上他的边距。
80 | int decoratedMeasuredWidth = getDecoratedMeasuredWidth(child);
81 | int decoratedMeasuredHeight = getDecoratedMeasuredHeight(child);
82 | // child.layout(0, top, decoratedMeasuredWidth, top + decoratedMeasuredHeight);
83 | Rect rect = frames.get(i);
84 | if (rect == null) {
85 | rect = new Rect();
86 | frames.put(i, rect);
87 | }
88 | if (xOffset + decoratedMeasuredWidth > getWidth()) {//换行
89 | xOffset = 0;
90 | yOffset += lineMaxHeight;
91 | lineMaxHeight = 0;
92 | }
93 | rect.set(xOffset, yOffset, xOffset + decoratedMeasuredWidth, yOffset + decoratedMeasuredHeight);
94 | xOffset += decoratedMeasuredWidth;
95 | lineMaxHeight = Math.max(decoratedMeasuredHeight, lineMaxHeight);
96 | }
97 | totalHeight = yOffset + lineMaxHeight;
98 |
99 | //把view进行一个轻量的移除
100 | fill(recycler, state);
101 | }
102 |
103 | private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) {
104 | Log.i("FlowLayoutManager","fill-->");
105 | //对于不显示的区域的item进行recycle
106 | //recyclerview显示区域
107 | detachAndScrapAttachedViews(recycler);
108 |
109 | Rect visibleRect = new Rect(0, verticalScrollOffset, getWidth(), verticalScrollOffset + getHeight());
110 | for (int i = 0; i < getItemCount(); i++) {
111 | boolean intersects = Rect.intersects(visibleRect, frames.get(i));
112 | View child = getChildAt(i);
113 | if (!intersects) {
114 | // //这个item不在可见区域内,所以要recycle
115 | // // TODO: by xk 2019/4/26 下午3:32 目前发现获取到的孩子是空的,因为上面调用了 detachAndScrapAttachedViews,但是也可能是因为没显示出来过。所以后续确认一下,这里是否有不为空的情况。
116 | if (child != null) {//事实证明,detach之后是get不到childview的
117 | removeAndRecycleView(child, recycler);
118 | }else{
119 | Log.i("FlowLayoutManager","fill-->null");
120 | }
121 | }
122 | }
123 | for (int i = 0; i < getItemCount(); i++) {
124 | boolean intersects = Rect.intersects(visibleRect, frames.get(i));
125 | if (intersects) {
126 | //item和可见区域有交集,所以这个item要attach
127 | View child = recycler.getViewForPosition(i);
128 | Rect rect = frames.get(i);
129 | measureChildWithMargins(child, 0, 0);
130 | addView(child);
131 | layoutDecorated(child, rect.left, rect.top - verticalScrollOffset, rect.right, rect.bottom - verticalScrollOffset);
132 | }
133 | }
134 |
135 | }
136 |
137 |
138 | @Override
139 | public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
140 | detachAndScrapAttachedViews(recycler);
141 | //实际滑动距离 dx
142 | int trval = dy;
143 | // 如果滑动到最顶部 往下滑 verticalScrollOffset -
144 | // 第一个坐标值 减 以前最后一个坐标值 //记死
145 | if (verticalScrollOffset + dy < 0) {
146 | trval = -verticalScrollOffset;
147 | } else if (verticalScrollOffset + dy > totalHeight - getHeight()) {
148 | // 如果滑动到最底部 往上滑 verticalScrollOffset +
149 | trval = totalHeight - getHeight() - verticalScrollOffset;
150 | }
151 | // 边界值判断
152 | verticalScrollOffset += trval;
153 |
154 | //平移容器内的item
155 | offsetChildrenVertical(trval);
156 | fill(recycler, state);
157 | return trval;
158 | }
159 |
160 | @Override
161 | public boolean canScrollVertically() {
162 | return true;
163 |
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/SpiderView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.util.AttributeSet;
9 | import android.view.View;
10 |
11 | /**
12 | * 游戏属性图
13 | */
14 | public class SpiderView extends View {
15 | private static final String TAG = "PathDemoView";
16 | private Paint mPaint;
17 | private int scale = 3;
18 | //辅助计算文字位置
19 | private float v;
20 |
21 | public SpiderView(Context context) {
22 | this(context, null);
23 | }
24 |
25 | public SpiderView(Context context, AttributeSet attrs) {
26 | super(context, attrs);
27 | init();
28 | }
29 |
30 | private void init() {
31 | mPaint = new Paint();
32 | mPaint.setAntiAlias(true);
33 | mPaint.setStyle(Paint.Style.STROKE);
34 | mPaint.setTextAlign(Paint.Align.CENTER);
35 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
36 | v = (fontMetrics.bottom + fontMetrics.top) / 2;
37 |
38 | mPaint.setTextSize(mPaint.getTextSize() * scale);
39 | }
40 |
41 |
42 | @Override
43 | protected void onDraw(Canvas canvas) {
44 |
45 | canvas.save();
46 | canvas.translate(getWidth() / 2, getHeight() / 2); // 移动坐标系到屏幕中心
47 |
48 | Path path = new Path();
49 |
50 | path.moveTo(10 * scale, 0 * scale);
51 | path.lineTo(5 * scale, (float) (5 * scale * Math.sqrt(3)));
52 | path.lineTo(-5 * scale, (float) (5 * scale * Math.sqrt(3)));
53 | path.lineTo(-10 * scale, 0 * scale);
54 | path.lineTo(-5 * scale, (float) (-5 * scale * Math.sqrt(3)));
55 | path.lineTo(5 * scale, (float) (-5 * scale * Math.sqrt(3)));
56 | path.close();
57 |
58 | path.moveTo(20 * scale, 0 * scale);
59 | path.lineTo(10 * scale, (float) (10 * scale * Math.sqrt(3)));
60 | path.lineTo(-10 * scale, (float) (10 * scale * Math.sqrt(3)));
61 | path.lineTo(-20 * scale, 0 * scale);
62 | path.lineTo(-10 * scale, (float) (-10 * scale * Math.sqrt(3)));
63 | path.lineTo(10 * scale, (float) (-10 * scale * Math.sqrt(3)));
64 | path.close();
65 |
66 | path.moveTo(30 * scale, 0 * scale);
67 | path.lineTo(15 * scale, (float) (15 * scale * Math.sqrt(3)));
68 | path.lineTo(-15 * scale, (float) (15 * scale * Math.sqrt(3)));
69 | path.lineTo(-30 * scale, 0 * scale);
70 | path.lineTo(-15 * scale, (float) (-15 * scale * Math.sqrt(3)));
71 | path.lineTo(15 * scale, (float) (-15 * scale * Math.sqrt(3)));
72 | path.close();
73 |
74 | path.moveTo(40 * scale, 0 * scale);
75 | path.lineTo(20 * scale, (float) (20 * scale * Math.sqrt(3)));
76 | path.lineTo(-20 * scale, (float) (20 * scale * Math.sqrt(3)));
77 | path.lineTo(-40 * scale, 0 * scale);
78 | path.lineTo(-20 * scale, (float) (-20 * scale * Math.sqrt(3)));
79 | path.lineTo(20 * scale, (float) (-20 * scale * Math.sqrt(3)));
80 | path.close();
81 |
82 | drawValue(canvas);
83 |
84 | canvas.drawPath(path, mPaint);
85 |
86 |
87 | canvas.drawText("A", 25 * scale, ((float) (-25 * Math.sqrt(3)) - v) * scale, mPaint);
88 | canvas.drawText("B", 50 * scale, (0 - v) * scale, mPaint);
89 | canvas.drawText("C", 25 * scale, ((float) (25 * Math.sqrt(3)) - v) * scale, mPaint);
90 | canvas.drawText("D", -25 * scale, ((float) (25 * Math.sqrt(3)) - v) * scale, mPaint);
91 | canvas.drawText("E", -50 * scale, (0 - v) * scale, mPaint);
92 | canvas.drawText("F", -25 * scale, ((float) (-25 * Math.sqrt(3)) - v) * scale, mPaint);
93 | canvas.restore();
94 |
95 | drawCoordinate(canvas);
96 |
97 | }
98 |
99 | private int aValue = 60;
100 | private int bValue = 50;
101 | private int cValue = 40;
102 | private int dValue = 58;
103 | private int eValue = 80;
104 | private int fValue = 30;
105 |
106 |
107 | private void drawValue(Canvas canvas) {
108 | Paint paint = new Paint(mPaint);
109 | paint.setColor(Color.GREEN);
110 | paint.setStyle(Paint.Style.FILL);
111 | paint.setStrokeWidth(2);
112 |
113 | Path path = new Path();
114 | //假设范围是0-100
115 | float ax = 20 * scale / 100f * aValue;
116 | float bx = 40 * scale / 100f * bValue;
117 | float cx = 20 * scale / 100f * cValue;
118 | float dx = -20 * scale / 100f * dValue;
119 | float ex = -40 * scale / 100f * eValue;
120 | float fx = -20 * scale / 100f * fValue;
121 |
122 |
123 | float ay = ((float) (-20 * Math.sqrt(3)) - v) * scale / 100f * aValue;
124 | float by = (0 - v) * scale / 100f * bValue;
125 | float cy = ((float) (20 * Math.sqrt(3)) - v) * scale / 100f * cValue;
126 | float dy = ((float) (20 * Math.sqrt(3)) - v) * scale / 100f * dValue;
127 | float ey = (0 - v) * scale / 100f * eValue;
128 | float fy = ((float) (-20 * Math.sqrt(3)) - v) * scale / 100f * fValue;
129 |
130 | path.moveTo(ax, ay);
131 | path.lineTo(bx, by);
132 | path.lineTo(cx, cy);
133 | path.lineTo(dx, dy);
134 | path.lineTo(ex, ey);
135 | path.lineTo(fx, fy);
136 | path.close();
137 | canvas.drawPath(path, paint);
138 | }
139 |
140 | private void drawCoordinate(Canvas canvas) {
141 | canvas.save();
142 | mPaint.setColor(0xff000000);
143 | canvas.translate(getWidth() / 2, getHeight() / 2);
144 | canvas.drawLine(-getWidth() / 2 + 5, 0, getWidth() / 2 - 5, 0, mPaint);
145 | canvas.drawLine(0, -getHeight() / 2 + 5, 0, getHeight() / 2 - 5, mPaint);
146 | mPaint.setColor(Color.RED); // 绘制合并后的路径
147 | canvas.restore();
148 | }
149 |
150 |
151 | public int getaValue() {
152 | return aValue;
153 | }
154 |
155 | public void setaValue(int aValue) {
156 | this.aValue = aValue;
157 | postInvalidate();
158 | }
159 |
160 |
161 | public int getbValue() {
162 | return bValue;
163 | }
164 |
165 | public void setbValue(int bValue) {
166 | this.bValue = bValue;
167 | postInvalidate();
168 |
169 | }
170 |
171 |
172 | public int getcValue() {
173 | return cValue;
174 | }
175 |
176 | public void setcValue(int cValue) {
177 | this.cValue = cValue;
178 | postInvalidate();
179 |
180 | }
181 |
182 |
183 | public int getdValue() {
184 | return dValue;
185 | }
186 |
187 | public void setdValue(int dValue) {
188 | this.dValue = dValue;
189 | postInvalidate();
190 |
191 | }
192 |
193 |
194 | public int geteValue() {
195 | return eValue;
196 | }
197 |
198 | public void seteValue(int eValue) {
199 | this.eValue = eValue;
200 | postInvalidate();
201 |
202 | }
203 |
204 | public int getfValue() {
205 | return fValue;
206 | }
207 |
208 | public void setfValue(int fValue) {
209 | this.fValue = fValue;
210 | postInvalidate();
211 |
212 | }
213 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/recyclerview/RecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom.recyclerview;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.IdRes;
5 | import android.util.AttributeSet;
6 | import android.util.Log;
7 | import android.view.MotionEvent;
8 | import android.view.View;
9 | import android.view.ViewConfiguration;
10 | import android.view.ViewGroup;
11 |
12 | import com.xk.customview.R;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * 大致实现了一个可以向上滚动,回收上面item,添加下面item的功能。不借助scrollBy方法的实现。着重理解原理
19 | * Created by xuekai on 2019/4/6.
20 | */
21 | public class RecyclerView extends ViewGroup {
22 | @IdRes
23 | private final int TAG_TYPE = 1;
24 | /**
25 | * 内容的偏移量,比如内容的头部距离view的顶部一段距离了,那段距离就是scrollY
26 | */
27 | int scrollY;
28 | private Adapter adapter;
29 | //当前显示的view
30 | private List viewList;
31 | //行数
32 | private int rowCount;
33 | //初始化???
34 | private boolean needRelayout;
35 | //recyclerview的宽度
36 | private int width;
37 | //recyclerview的高度
38 | private int height;
39 | //孩子们的高度
40 | private int[] heights;
41 | //回收池
42 | private Recycler recycler;
43 | //第一个显示的item的position,该变量用来记录第一个显示的item的position。在onLayout的时候,从这个position开始向后布局
44 | private int firstVisiablePosition = 0;
45 | //手指按下的Y
46 | private float downY;
47 | //上一次事件的Y
48 | private float lastY;
49 | private int scaledTouchSlop;
50 |
51 | public RecyclerView(Context context) {
52 | this(context, null);
53 | }
54 |
55 | public RecyclerView(Context context, AttributeSet attrs) {
56 | this(context, attrs, 0);
57 | }
58 |
59 | public RecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
60 | super(context, attrs, defStyleAttr);
61 | }
62 |
63 | public void setAdapter(Adapter adapter) {
64 | this.adapter = adapter;
65 | init();
66 | requestLayout();
67 | }
68 |
69 | private void init() {
70 | ViewConfiguration vc = ViewConfiguration.get(getContext());
71 | scaledTouchSlop = vc.getScaledTouchSlop();
72 | needRelayout = true;
73 | viewList = new ArrayList<>();
74 | if (adapter != null) {
75 | recycler = new Recycler(adapter.getViewTypeCount());
76 | }
77 | }
78 |
79 |
80 | @Override
81 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
82 | if (adapter != null && needRelayout) {
83 | needRelayout = false;
84 | viewList.clear();
85 | removeAllViews();
86 | //正在摆放的孩子的top。
87 | // TODO: by xk 2019/4/6 18:41 如果只是第一次初始化,top为0,但是如果实在刷新的话,可能第一个显示的孩子的top并不一定是0
88 | int top = 0;
89 | if (adapter != null) {
90 | width = r - l;
91 | height = b - t;
92 | // TODO: by xk 2019/4/6 18:43 为啥从0开始,应该是从firstVisiableView开始
93 | //开始循环摆放子控件,但是不是从0开始摆所有,而是只摆放边界之内的。通过top<=height来判断
94 | for (int i = 0; i < adapter.getCount() && top <= height; i++) {
95 | makeAndStepView(0, top, r, top + heights[i], i);
96 | top += heights[i];
97 | }
98 | }
99 | }
100 |
101 | }
102 |
103 | /**
104 | * 创建、获取并且安装一个子View
105 | */
106 | private void makeAndStepView(int l, int t, int r, int b, int position) {
107 | View view = obtainView(position);
108 | //测量孩子。说实话,我们下面layout的时候,位置父亲已经制定好了,不需要测量了。毕竟测量出来的数字也就是为了layout使用。但是最好还是测量一下,毕竟子view自定义控件里面或许自己需要。
109 | view.measure(MeasureSpec.makeMeasureSpec(r - l, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(b - t, MeasureSpec.EXACTLY));
110 | view.layout(l, t, r, b);
111 | }
112 |
113 | private View obtainView(int position) {
114 | //通过回收池拿到了一个View,可能为空。然后让adapter去处理。如果是空的,getView中创建。如果不为空,getView设置一下数据直接给返回。
115 | View convertView = recycler.getViewByItemType(adapter.getItemType(position));
116 | View view = adapter.getView(position, convertView, this);
117 | if (view == null) {
118 | throw new RuntimeException("getView返回的view为空了");
119 | }
120 | //???为啥要给第一个添加
121 | viewList.add(view);
122 | view.setTag(R.id.tag_type, adapter.getItemType(position));
123 | addView(view);
124 | return view;
125 | }
126 |
127 | @Override
128 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
129 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
130 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
131 | int tempH = 0;
132 | //把所有孩子的高度遍历放到heights集合中。同时,计算出recyclerview的高度。
133 | if (adapter != null) {
134 | heights = new int[adapter.getCount()];
135 | for (int i = 0; i < adapter.getCount(); i++) {
136 | heights[i] = adapter.getHeight();
137 | tempH += heights[i];
138 | }
139 | }
140 | //上一步计算的高度和控件父亲约束的高度取最小值
141 | int minHeight = Math.min(heightSize, tempH);
142 | setMeasuredDimension(widthSize,
143 | minHeight);
144 | }
145 |
146 | @Override
147 | public boolean onInterceptTouchEvent(MotionEvent ev) {
148 | switch (ev.getAction()) {
149 | case MotionEvent.ACTION_DOWN:
150 | lastY = downY = ev.getY();
151 | Log.i("RecyclerView", "onInterceptTouchEvent-->down");
152 | break;
153 | case MotionEvent.ACTION_MOVE:
154 | float diffY = downY - ev.getY();
155 | if (Math.abs(diffY) > Math.abs(scaledTouchSlop)) {
156 | Log.i("RecyclerView", "onInterceptTouchEvent-->move");
157 | //拦截
158 | return true;
159 | }
160 | break;
161 | default:
162 | break;
163 | }
164 | return super.onInterceptTouchEvent(ev);
165 | }
166 |
167 | @Override
168 | public boolean onTouchEvent(MotionEvent event) {
169 | Log.i("RecyclerView", "onTouchEvent" + event.getAction());
170 |
171 | switch (event.getAction()) {
172 | case MotionEvent.ACTION_DOWN:
173 | break;
174 | case MotionEvent.ACTION_MOVE:
175 | int diffY = (int) (lastY - event.getY());
176 | lastY = event.getY();
177 | scrollBy(0, diffY);
178 | break;
179 | default:
180 | break;
181 | }
182 | return super.onTouchEvent(event);
183 | }
184 |
185 |
186 | @Override
187 | public void scrollBy(int x, int y) {
188 | scrollY += y;
189 | //添加孩子、删除孩子
190 | View firstVisiableView = viewList.get(0);
191 | if (y > 0) {
192 |
193 | if (-firstVisiableView.getTop() > adapter.getHeight()) {
194 | //这个被划出去了
195 | removeTopView(firstVisiableView);
196 | }
197 |
198 | if (needAddBottom(y)) {
199 | addBottomView();
200 | }
201 |
202 | }
203 |
204 | firstTop = viewList.get(0).getTop() - y;
205 | reLayoutChild();
206 | }
207 |
208 | private void addBottomView() {
209 | int lastPosition = firstVisiablePosition + viewList.size();
210 | View view = obtainView(lastPosition);
211 | //测量孩子。说实话,我们下面layout的时候,位置父亲已经制定好了,不需要测量了。毕竟测量出来的数字也就是为了layout使用。但是最好还是测量一下,毕竟子view自定义控件里面或许自己需要。
212 | view.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(adapter.getHeight(), MeasureSpec.EXACTLY));
213 | }
214 |
215 | private boolean needAddBottom(int y) {
216 | int firstTop = viewList.get(0).getTop() - y;
217 | int height = 0;
218 | for (View view : viewList) {
219 | height += view.getHeight();
220 | }
221 | //屏幕展示的内容的高度
222 | int showHeight = height - firstTop;
223 | return showHeight < getHeight();
224 | }
225 |
226 | private void removeTopView(View view) {
227 | removeView(view);
228 | viewList.remove(0);
229 | firstVisiablePosition++;
230 | recycler.saveView(view, (Integer) view.getTag(R.id.tag_type));
231 | }
232 |
233 | int firstTop = 0;
234 |
235 | private void reLayoutChild() {
236 | int top = firstTop;
237 | for (View view : viewList) {
238 | view.layout(0, top, width, adapter.getHeight() + top);
239 | top += adapter.getHeight();
240 | }
241 | }
242 |
243 |
244 | @Override
245 | public void removeView(View view) {
246 | super.removeView(view);
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xk/customview/custom/LeafProgressBar.java:
--------------------------------------------------------------------------------
1 | package com.xk.customview.custom;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.graphics.drawable.BitmapDrawable;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.View;
12 |
13 | import com.xk.customview.R;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.Random;
18 |
19 | /**
20 | * 模仿http://blog.csdn.net/tianjian4592/article/details/44538605
21 | * 实现方法不同,原来的版本叶子是随机的,这个版本叶子是由进度确定的,风扇速度由进度变化来决定
22 | * Created by xuekai on 2017/2/16.
23 | */
24 |
25 | public class LeafProgressBar extends View {
26 |
27 | private Random random;
28 | private static final String TAG = "CustomProgressBar";
29 | private int width = 0;
30 | private int height = 0;
31 | private int frameWidth = 0;
32 | private int frameHeight = 0;
33 | private Bitmap frameBitmap;
34 | private Bitmap leaf;
35 | private Bitmap fengshan;
36 | private Paint mBitmapPaint;
37 | private Paint mWhitePaint;
38 | private Paint mOrangePaint;
39 | // 用于控制绘制的进度条距离左/上/下的距离
40 | private static final int LEFT_MARGIN = 6;
41 | // 用于控制绘制的进度条距离右的距离
42 | private static final int RIGHT_MARGIN = 34;
43 | // 用于控制绘制的进度条距离上、下的距离
44 | private static final int TOP_BOTTOM_MARGIN = 5;
45 | // 淡白色
46 | private static final int WHITE_COLOR = 0xfffde399;
47 | // 橙色
48 | private static final int ORANGE_COLOR = 0xffffa800;
49 |
50 |
51 | private int leaf_count = 0;
52 |
53 | private int progress_totalWidth;
54 |
55 | private List leafs = new ArrayList<>();
56 |
57 | private int currentProgress = 0;
58 | private int fengshanHeight = 0;
59 | private int fengshanWidth = 0;
60 | private float whiteRectWidth;
61 | private float orangeRectWidth;
62 |
63 | private static final int FENGSHANSPEED_MIN = 3;
64 | private static final int FENGSHANSPEED_MAX = 8;
65 |
66 | private float fengShanSpeed = FENGSHANSPEED_MIN;
67 | private int fengShanRoate = 0;
68 | //两个叶子之间的最小距离
69 | private int distance=50;
70 |
71 | public LeafProgressBar(Context context) {
72 | this(context, null);
73 | }
74 |
75 | public LeafProgressBar(Context context, AttributeSet attrs) {
76 | super(context, attrs);
77 | init();
78 | }
79 |
80 | private void init() {
81 | setBackgroundColor(0xffffa800);
82 | random = new Random();
83 | frameBitmap = ((BitmapDrawable) getResources().getDrawable(R.mipmap.leaf_kuang)).getBitmap();
84 | leaf = ((BitmapDrawable) getResources().getDrawable(R.mipmap.leaf)).getBitmap();
85 | fengshan = ((BitmapDrawable) getResources().getDrawable(R.mipmap.fengshan)).getBitmap();
86 |
87 | frameWidth = frameBitmap.getWidth();
88 | frameHeight = frameBitmap.getHeight();
89 | fengshanWidth = fengshan.getWidth();
90 | fengshanHeight = fengshan.getHeight();
91 |
92 | mBitmapPaint = new Paint();
93 | mBitmapPaint.setAntiAlias(true);
94 | mBitmapPaint.setDither(true);
95 | mBitmapPaint.setFilterBitmap(true);
96 |
97 | mWhitePaint = new Paint();
98 | mWhitePaint.setAntiAlias(true);
99 | mWhitePaint.setColor(WHITE_COLOR);
100 |
101 | mOrangePaint = new Paint();
102 | mOrangePaint.setAntiAlias(true);
103 | mOrangePaint.setColor(ORANGE_COLOR);
104 |
105 | progress_totalWidth = frameWidth - RIGHT_MARGIN - LEFT_MARGIN;
106 | }
107 |
108 |
109 | @Override
110 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
111 | width = w;
112 | height = h;
113 | }
114 |
115 |
116 | @Override
117 | protected void onDraw(Canvas canvas) {
118 | drawProgressAndLeaf(canvas);
119 |
120 | drawFrame(canvas);
121 |
122 | drawFengShan(canvas);
123 | invalidate();
124 |
125 | }
126 |
127 | private void drawFengShan(Canvas canvas) {
128 | canvas.save();
129 | canvas.translate(width / 2 + frameWidth / 2 - fengshanWidth / 2 - 4, height / 2);
130 | fengShanRoate += (int)fengShanSpeed;
131 | Log.e(TAG, "drawFengShan" + fengShanSpeed);
132 | fengShanRoate = fengShanRoate % 360;
133 | canvas.rotate(fengShanRoate, 0, 0);
134 | canvas.drawBitmap(fengshan, -fengshanWidth / 2, -fengshanHeight / 2, mBitmapPaint);
135 | canvas.restore();
136 | }
137 |
138 | private void drawProgressAndLeaf(Canvas canvas) {
139 | canvas.save();
140 | canvas.translate(width / 2, height / 2);
141 |
142 | orangeRectWidth = progress_totalWidth / 100f * currentProgress;
143 | whiteRectWidth = progress_totalWidth - orangeRectWidth;
144 |
145 |
146 | //画白色矩形
147 | canvas.drawRect(-frameWidth / 2 + LEFT_MARGIN + orangeRectWidth, -frameHeight / 2 + TOP_BOTTOM_MARGIN, frameWidth / 2 - RIGHT_MARGIN, frameHeight / 2 - TOP_BOTTOM_MARGIN, mWhitePaint);
148 |
149 | clearAbandonLeaf();
150 |
151 |
152 | //最后一个叶子的x是最右边的,速度加1
153 | if (leafs.size()>0) {
154 | if (leafs.get(leafs.size()-1).x==frameWidth / 2 - RIGHT_MARGIN) {
155 | fenShanSpeedUp();
156 | }else{
157 | fenShanSpeedDown();
158 | }
159 | }else{
160 | fenShanSpeedDown();
161 | }
162 | for (Leaf leaf1 : leafs) {
163 |
164 |
165 | //画叶子
166 | canvas.save();
167 | canvas.rotate(leaf1.roate, leaf1.x + leaf.getWidth() / 2, leaf1.y);
168 | canvas.drawBitmap(leaf, leaf1.x, leaf1.y, mBitmapPaint);
169 | leaf1.nextX();
170 | leaf1.nextRoate();
171 | leaf1.nextY();
172 | canvas.restore();
173 |
174 | }
175 |
176 |
177 | //画橘色矩形
178 | canvas.drawRect(-frameWidth / 2 + LEFT_MARGIN, -frameHeight / 2 + TOP_BOTTOM_MARGIN, frameWidth / 2 - RIGHT_MARGIN - whiteRectWidth, frameHeight / 2 - TOP_BOTTOM_MARGIN, mOrangePaint);
179 |
180 |
181 | canvas.restore();
182 | }
183 |
184 | /**
185 | * 清除废弃的叶子
186 | */
187 | private void clearAbandonLeaf() {
188 | int index = -1;
189 | for (int i = 0; i < leafs.size(); i++) {
190 | if (leafs.get(i).x <= frameWidth / 2 - RIGHT_MARGIN - whiteRectWidth - leaf.getWidth() || leafs.get(i).x <= -frameWidth / 2 + LEFT_MARGIN) {
191 | index = i;
192 | break;
193 | }
194 | }
195 | if (index != -1) {
196 | for (int i = index; i >= 0; i--) {
197 | leafs.remove(i);
198 | Log.e(TAG, "clearAbandonLeaf");
199 | }
200 | }
201 | }
202 |
203 | public void drawFrame(Canvas canvas) {
204 | canvas.save();
205 | canvas.translate(width / 2, height / 2);
206 | canvas.drawBitmap(frameBitmap, -frameWidth / 2, -frameHeight / 2, mBitmapPaint);
207 | canvas.restore();
208 | }
209 |
210 | public void setCurrentProgress(int currentProgress) {
211 | this.currentProgress = currentProgress;
212 | if (leafs.size() > 0) {
213 | Leaf lastLeaf = leafs.get(leafs.size() - 1);
214 | if (frameWidth / 2 - RIGHT_MARGIN - lastLeaf.x > distance) {
215 | Leaf leaf = new Leaf();
216 | leafs.add(leaf);
217 | }
218 | } else {
219 | Leaf leaf = new Leaf();
220 | leafs.add(leaf);
221 | }
222 |
223 |
224 | }
225 |
226 | public int getCurrentProgress() {
227 | return currentProgress;
228 | }
229 |
230 |
231 | //进度加1,速度加2,有上限
232 | private void fenShanSpeedUp(){
233 | if (fengShanSpeed>=FENGSHANSPEED_MAX) {
234 | fengShanSpeed=FENGSHANSPEED_MAX;
235 | }else{
236 | fengShanSpeed+=4;
237 | }
238 | }
239 | //进度不动,速度减1,有下限
240 | private void fenShanSpeedDown(){
241 | if (fengShanSpeed<=FENGSHANSPEED_MIN+0.1) {
242 | fengShanSpeed=FENGSHANSPEED_MIN;
243 | }else{
244 | fengShanSpeed-=0.1f;
245 | }
246 | }
247 |
248 |
249 | class Leaf {
250 | public Leaf() {
251 | roateDirection = random.nextBoolean();
252 | amplitude = random.nextBoolean();
253 | }
254 |
255 | //旋转方向
256 | boolean roateDirection;
257 |
258 |
259 | //振幅方向
260 | boolean amplitude;
261 |
262 |
263 | int x = frameWidth / 2 - RIGHT_MARGIN;
264 | int y = 0;
265 | float roate = 0;
266 |
267 | public void nextRoate() {
268 | if (roateDirection) {
269 | roate++;
270 |
271 | } else {
272 | roate--;
273 | }
274 | }
275 |
276 | public void nextX() {
277 | x -= 1;
278 | }
279 |
280 | public void nextY() {
281 | if (amplitude) {
282 | y = (int) (-(frameHeight / 10) * Math.sin(x / 8.5));
283 | } else {
284 | y = (int) ((frameHeight / 10) * Math.sin(x / 8.5));
285 | }
286 |
287 | }
288 | }
289 |
290 | }
291 |
--------------------------------------------------------------------------------