一个不错的loading加载效果,弹性收缩,效果不错,学习android动画的朋友可以下载来研究研究
本例子其实由SeekBar实现,由MetaballView,MetaballDebugView实现动画效果.
当滑动到有一个位置的时候设置选中和未选中状态.
metaballView.setPaintMode();
debugMetaballView.setPaintMode();
设置SeekBar 的进度debugMetaballView.setMaxDistance(progress);
布局引入:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#001d30"
android:clipChildren="false"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<com.dodola.animview.MetaballView
android:id="@+id/metaball"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true" />
<com.dodola.animview.MetaballDebugView
android:id="@+id/debug_metaball"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/metaball"
android:layout_alignRight="@+id/metaball"
android:layout_below="@+id/metaball"
android:layout_marginTop="20dp" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/debug_metaball"
android:text="Debug test"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:clickable="true"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="最大间距"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:max="400" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="贝塞尔曲线角度"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/seekBar2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="200" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="贝塞尔曲线控制点长度比率"
android:textColor="@android:color/white" />
<SeekBar
android:id="@+id/seekBar3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="500" />
</LinearLayout>
</RelativeLayout>
主要代码如下:
package com.dodola.animview; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ProgressBar; import android.widget.SeekBar; public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener { private MetaballView metaballView; private MetaballDebugView debugMetaballView; private SeekBar seekBar, seekBar2, seekBar3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); metaballView = (MetaballView) this.findViewById(R.id.metaball); debugMetaballView = (MetaballDebugView) findViewById(R.id.debug_metaball); seekBar = (SeekBar) findViewById(R.id.seekBar); seekBar2 = (SeekBar) findViewById(R.id.seekBar2); seekBar3 = (SeekBar) findViewById(R.id.seekBar3); seekBar.setOnSeekBarChangeListener(this); seekBar2.setOnSeekBarChangeListener(this); seekBar3.setOnSeekBarChangeListener(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_fill) { metaballView.setPaintMode(1); debugMetaballView.setPaintMode(1); return true; } else if (id == R.id.action_strock) { metaballView.setPaintMode(0); debugMetaballView.setPaintMode(0); return true; } return super.onOptionsItemSelected(item); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { switch (seekBar.getId()) { case R.id.seekBar: debugMetaballView.setMaxDistance(progress); break; case R.id.seekBar2: debugMetaballView.setMv(progress / 100f); break; case R.id.seekBar3: debugMetaballView.setHandleLenRate(progress / 100f); break; } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }
自定义页面MetaballDebugView,主要代码如下
package com.dodola.animview; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import java.util.ArrayList; import java.util.Arrays; /** * Created by dodola on 15/7/27. */ public class MetaballDebugView extends View { private Paint paint = new Paint(); private float handleLenRate = 2f; private final float radius = 60; private final float SCALE_RATE = 0.3f; private ArrayList<Circle> circlePaths = new ArrayList<>(); private float mv = 0.6f; private float maxDistance = radius * 4; public MetaballDebugView(Context context) { super(context); init(); } public MetaballDebugView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MetaballDebugView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public float getMv() { return mv; } public void setMv(float mv) { this.mv = mv; invalidate(); } public float getMaxDistance() { return maxDistance; } public void setMaxDistance(float maxDistance) { this.maxDistance = maxDistance; invalidate(); } public float getHandleLenRate() { return handleLenRate; } public void setHandleLenRate(float handleLenRate) { this.handleLenRate = handleLenRate; invalidate(); } private class Circle { float[] center; float radius; } public void setPaintMode(int mode) { paint.setStyle(mode == 0 ? Paint.Style.STROKE : Paint.Style.FILL); invalidate(); } private void init() { paint.setColor(0xff4db9ff); paint.setStyle(Paint.Style.FILL); paint.setAntiAlias(true); } private void initMetaballs() { Circle circlePath = new Circle(); circlePath.center = new float[]{(radius), radius}; circlePath.radius = radius; circlePaths.add(circlePath); circlePath = new Circle(); circlePath.center = new float[]{this.getMeasuredWidth() / 2, this.getMeasuredHeight() / 3}; circlePath.radius = radius; circlePaths.add(circlePath); } private float[] getVector(float radians, float length) { float x = (float) (Math.cos(radians) * length); float y = (float) (Math.sin(radians) * length); return new float[]{ x, y }; } /** * @param canvas 画布 * @param j * @param i * @param v 控制两个圆连接时候长度,间接控制连接线的粗细,该值为1的时候连接线为直线 * @param handle_len_rate * @param maxDistance */ private void metaball(Canvas canvas, int j, int i, float v, float handle_len_rate, float maxDistance) { final Circle circle1 = circlePaths.get(i); final Circle circle2 = circlePaths.get(j); RectF ball1 = new RectF(); ball1.left = circle1.center[0] - circle1.radius; ball1.top = circle1.center[1] - circle1.radius; ball1.right = ball1.left + circle1.radius * 2; ball1.bottom = ball1.top + circle1.radius * 2; RectF ball2 = new RectF(); ball2.left = circle2.center[0] - circle2.radius; ball2.top = circle2.center[1] - circle2.radius; ball2.right = ball2.left + circle2.radius * 2; ball2.bottom = ball2.top + circle2.radius * 2; float[] center1 = new float[]{ ball1.centerX(), ball1.centerY() }; float[] center2 = new float[]{ ball2.centerX(), ball2.centerY() }; float d = getDistance(center1, center2); float radius1 = ball1.width() / 2; float radius2 = ball2.width() / 2; float pi2 = (float) (Math.PI / 2); float u1, u2; if (d > maxDistance) { canvas.drawCircle(ball2.centerX(), ball2.centerY(), circle2.radius, paint); } else { float scale2 = 1 + SCALE_RATE * (1 - d / maxDistance); radius2 *= scale2; canvas.drawCircle(ball2.centerX(), ball2.centerY(), radius2, paint); } Log.d("Metaball_radius", "radius1:" + radius1 + ",radius2:" + radius2); if (radius1 == 0 || radius2 == 0) { return; } if (d > maxDistance || d <= Math.abs(radius1 - radius2)) { return; } else if (d < radius1 + radius2) { u1 = (float) Math.acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)); u2 = (float) Math.acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)); } else { u1 = 0; u2 = 0; } Log.d("Metaball", "center2:" + Arrays.toString(center2) + ",center1:" + Arrays.toString(center1)); float[] centermin = new float[]{center2[0] - center1[0], center2[1] - center1[1]}; float angle1 = (float) Math.atan2(centermin[1], centermin[0]); float angle2 = (float) Math.acos((radius1 - radius2) / d); float angle1a = angle1 + u1 + (angle2 - u1) * v; float angle1b = angle1 - u1 - (angle2 - u1) * v; float angle2a = (float) (angle1 + Math.PI - u2 - (Math.PI - u2 - angle2) * v); float angle2b = (float) (angle1 - Math.PI + u2 + (Math.PI - u2 - angle2) * v); Log.d("Metaball", "angle1:" + angle1 + ",angle2:" + angle2 + ",angle1a:" + angle1a + ",angle1b:" + angle1b + ",angle2a:" + angle2a + ",angle2b:" + angle2b); float[] p1a1 = getVector(angle1a, radius1); float[] p1b1 = getVector(angle1b, radius1); float[] p2a1 = getVector(angle2a, radius2); float[] p2b1 = getVector(angle2b, radius2); float[] p1a = new float[]{p1a1[0] + center1[0], p1a1[1] + center1[1]}; float[] p1b = new float[]{p1b1[0] + center1[0], p1b1[1] + center1[1]}; float[] p2a = new float[]{p2a1[0] + center2[0], p2a1[1] + center2[1]}; float[] p2b = new float[]{p2b1[0] + center2[0], p2b1[1] + center2[1]}; Log.d("Metaball", "p1a:" + Arrays.toString(p1a) + ",p1b:" + Arrays.toString(p1b) + ",p2a:" + Arrays.toString(p2a) + ",p2b:" + Arrays.toString(p2b)); float[] p1_p2 = new float[]{p1a[0] - p2a[0], p1a[1] - p2a[1]}; float totalRadius = (radius1 + radius2); float d2 = Math.min(v * handle_len_rate, getLength(p1_p2) / totalRadius); d2 *= Math.min(1, d * 2 / (radius1 + radius2)); Log.d("Metaball", "d2:" + d2); radius1 *= d2; radius2 *= d2; float[] sp1 = getVector(angle1a - pi2, radius1); float[] sp2 = getVector(angle2a + pi2, radius2); float[] sp3 = getVector(angle2b - pi2, radius2); float[] sp4 = getVector(angle1b + pi2, radius1); Log.d("Metaball", "sp1:" + Arrays.toString(sp1) + ",sp2:" + Arrays.toString(sp2) + ",sp3:" + Arrays.toString(sp3) + ",sp4:" + Arrays.toString(sp4)); Path path1 = new Path(); path1.moveTo(p1a[0], p1a[1]); path1.cubicTo(p1a[0] + sp1[0], p1a[1] + sp1[1], p2a[0] + sp2[0], p2a[1] + sp2[1], p2a[0], p2a[1]); path1.lineTo(p2b[0], p2b[1]); path1.cubicTo(p2b[0] + sp3[0], p2b[1] + sp3[1], p1b[0] + sp4[0], p1b[1] + sp4[1], p1b[0], p1b[1]); path1.lineTo(p1a[0], p1a[1]); path1.close(); canvas.drawPath(path1, paint); } private float getLength(float[] b) { return (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]); } private float getDistance(float[] b1, float[] b2) { float x = b1[0] - b2[0]; float y = b1[1] - b2[1]; float d = x * x + y * y; return (float) Math.sqrt(d); } //测试用 @Override public boolean onTouchEvent(MotionEvent event) { Circle circle = circlePaths.get(0); circle.center[0] = event.getX(); circle.center[1] = event.getY(); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (circlePaths.size() == 0) { initMetaballs(); } final Circle circle1 = circlePaths.get(0); RectF ball1 = new RectF(); ball1.left = circle1.center[0] - circle1.radius; ball1.top = circle1.center[1] - circle1.radius; ball1.right = ball1.left + circle1.radius * 2; ball1.bottom = ball1.top + circle1.radius * 2; canvas.drawCircle(ball1.centerX(), ball1.centerY(), circle1.radius, paint); for (int i = 1; i < circlePaths.size(); i++) { metaball(canvas, i, 0, mv, handleLenRate, maxDistance); } } }
自定义MetaballView,主要代码如下
控制两个圆连接时候长度,间接控制连接线的粗细,该值为1的时候连接线为直线
文章原创《IT蓝豹》http://www.itlanbao.com/
package com.dodola.animview; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LightingColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.Transformation; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; /** * Created by dodola on 15/7/27. */ public class MetaballView extends View { private Paint paint = new Paint(); private float handle_len_rate = 2f; private float radius = 30; private final int ITEM_COUNT = 6; private final int ITEM_DIVIDER = 60; private final float SCALE_RATE = 0.3f; private float maxLength; private ArrayList<Circle> circlePaths = new ArrayList<>(); private float mInterpolatedTime; private MoveAnimation wa; private Circle circle; public MetaballView(Context context) { super(context); init(); } public MetaballView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MetaballView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private class Circle { float[] center; float radius; } public void setPaintMode(int mode) { paint.setStyle(mode == 0 ? Paint.Style.STROKE : Paint.Style.FILL); invalidate(); } private void init() { paint.setColor(0xff4db9ff); paint.setStyle(Paint.Style.FILL); paint.setAntiAlias(true); Circle circlePath = new Circle(); circlePath.center = new float[]{(radius + ITEM_DIVIDER), radius * (1f + SCALE_RATE)}; circlePath.radius = radius / 4 * 3; circlePaths.add(circlePath); for (int i = 1; i < ITEM_COUNT; i++) { circlePath = new Circle(); circlePath.center = new float[]{(radius * 2 + ITEM_DIVIDER) * i, radius * (1f + SCALE_RATE)}; circlePath.radius = radius; circlePaths.add(circlePath); } maxLength = (radius * 2 + ITEM_DIVIDER) * ITEM_COUNT; } private float[] getVector(float radians, float length) { float x = (float) (Math.cos(radians) * length); float y = (float) (Math.sin(radians) * length); return new float[]{ x, y }; } private class MoveAnimation extends Animation { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); mInterpolatedTime = interpolatedTime; invalidate(); } } /** * @param canvas 画布 * @param j * @param i * @param v 控制两个圆连接时候长度,间接控制连接线的粗细,该值为1的时候连接线为直线 * @param handle_len_rate * @param maxDistance */ private void metaball(Canvas canvas, int j, int i, float v, float handle_len_rate, float maxDistance) { final Circle circle1 = circlePaths.get(i); final Circle circle2 = circlePaths.get(j); RectF ball1 = new RectF(); ball1.left = circle1.center[0] - circle1.radius; ball1.top = circle1.center[1] - circle1.radius; ball1.right = ball1.left + circle1.radius * 2; ball1.bottom = ball1.top + circle1.radius * 2; RectF ball2 = new RectF(); ball2.left = circle2.center[0] - circle2.radius; ball2.top = circle2.center[1] - circle2.radius; ball2.right = ball2.left + circle2.radius * 2; ball2.bottom = ball2.top + circle2.radius * 2; float[] center1 = new float[]{ ball1.centerX(), ball1.centerY() }; float[] center2 = new float[]{ ball2.centerX(), ball2.centerY() }; float d = getDistance(center1, center2); float radius1 = ball1.width() / 2; float radius2 = ball2.width() / 2; float pi2 = (float) (Math.PI / 2); float u1, u2; if (d > maxDistance) { // canvas.drawCircle(ball1.centerX(), ball1.centerY(), circle1.radius, paint); canvas.drawCircle(ball2.centerX(), ball2.centerY(), circle2.radius, paint); } else { float scale2 = 1 + SCALE_RATE * (1 - d / maxDistance); float scale1 = 1 - SCALE_RATE * (1 - d / maxDistance); radius2 *= scale2; // radius1 *= scale1; // canvas.drawCircle(ball1.centerX(), ball1.centerY(), radius1, paint); canvas.drawCircle(ball2.centerX(), ball2.centerY(), radius2, paint); } // Log.d("Metaball_radius", "radius1:" + radius1 + ",radius2:" + radius2); if (radius1 == 0 || radius2 == 0) { return; } if (d > maxDistance || d <= Math.abs(radius1 - radius2)) { return; } else if (d < radius1 + radius2) { u1 = (float) Math.acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)); u2 = (float) Math.acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)); } else { u1 = 0; u2 = 0; } // Log.d("Metaball", "center2:" + Arrays.toString(center2) + ",center1:" + Arrays.toString(center1)); float[] centermin = new float[]{center2[0] - center1[0], center2[1] - center1[1]}; float angle1 = (float) Math.atan2(centermin[1], centermin[0]); float angle2 = (float) Math.acos((radius1 - radius2) / d); float angle1a = angle1 + u1 + (angle2 - u1) * v; float angle1b = angle1 - u1 - (angle2 - u1) * v; float angle2a = (float) (angle1 + Math.PI - u2 - (Math.PI - u2 - angle2) * v); float angle2b = (float) (angle1 - Math.PI + u2 + (Math.PI - u2 - angle2) * v); // Log.d("Metaball", "angle1:" + angle1 + ",angle2:" + angle2 + ",angle1a:" + angle1a + ",angle1b:" + angle1b + ",angle2a:" + angle2a + ",angle2b:" + angle2b); float[] p1a1 = getVector(angle1a, radius1); float[] p1b1 = getVector(angle1b, radius1); float[] p2a1 = getVector(angle2a, radius2); float[] p2b1 = getVector(angle2b, radius2); float[] p1a = new float[]{p1a1[0] + center1[0], p1a1[1] + center1[1]}; float[] p1b = new float[]{p1b1[0] + center1[0], p1b1[1] + center1[1]}; float[] p2a = new float[]{p2a1[0] + center2[0], p2a1[1] + center2[1]}; float[] p2b = new float[]{p2b1[0] + center2[0], p2b1[1] + center2[1]}; // Log.d("Metaball", "p1a:" + Arrays.toString(p1a) + ",p1b:" + Arrays.toString(p1b) + ",p2a:" + Arrays.toString(p2a) + ",p2b:" + Arrays.toString(p2b)); float[] p1_p2 = new float[]{p1a[0] - p2a[0], p1a[1] - p2a[1]}; float totalRadius = (radius1 + radius2); float d2 = Math.min(v * handle_len_rate, getLength(p1_p2) / totalRadius); d2 *= Math.min(1, d * 2 / (radius1 + radius2)); // Log.d("Metaball", "d2:" + d2); radius1 *= d2; radius2 *= d2; float[] sp1 = getVector(angle1a - pi2, radius1); float[] sp2 = getVector(angle2a + pi2, radius2); float[] sp3 = getVector(angle2b - pi2, radius2); float[] sp4 = getVector(angle1b + pi2, radius1); // Log.d("Metaball", "sp1:" + Arrays.toString(sp1) + ",sp2:" + Arrays.toString(sp2) + ",sp3:" + Arrays.toString(sp3) + ",sp4:" + Arrays.toString(sp4)); Path path1 = new Path(); path1.moveTo(p1a[0], p1a[1]); path1.cubicTo(p1a[0] + sp1[0], p1a[1] + sp1[1], p2a[0] + sp2[0], p2a[1] + sp2[1], p2a[0], p2a[1]); path1.lineTo(p2b[0], p2b[1]); path1.cubicTo(p2b[0] + sp3[0], p2b[1] + sp3[1], p1b[0] + sp4[0], p1b[1] + sp4[1], p1b[0], p1b[1]); path1.lineTo(p1a[0], p1a[1]); path1.close(); canvas.drawPath(path1, paint); } private float getLength(float[] b) { return (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]); } private float getDistance(float[] b1, float[] b2) { float x = b1[0] - b2[0]; float y = b1[1] - b2[1]; float d = x * x + y * y; return (float) Math.sqrt(d); } //测试用 // @Override // public boolean onTouchEvent(MotionEvent event) { // switch (event.getAction()) { // case MotionEvent.ACTION_DOWN: // break; // case MotionEvent.ACTION_MOVE: // Circle circle = circlePaths.get(0); // circle.center[0] = event.getX(); // circle.center[1] = event.getY(); // invalidate(); // break; // case MotionEvent.ACTION_UP: // break; // } // // return true; // } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); circle = circlePaths.get(0); circle.center[0] = maxLength * mInterpolatedTime; RectF ball1 = new RectF(); ball1.left = circle.center[0] - circle.radius; ball1.top = circle.center[1] - circle.radius; ball1.right = ball1.left + circle.radius * 2; ball1.bottom = ball1.top + circle.radius * 2; canvas.drawCircle(ball1.centerX(), ball1.centerY(), circle.radius, paint); for (int i = 1, l = circlePaths.size(); i < l; i++) { metaball(canvas, i, 0, 0.6f, handle_len_rate, radius * 4f); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(resolveSizeAndState((int) (ITEM_COUNT * (radius * 2 + ITEM_DIVIDER)), widthMeasureSpec, 0), resolveSizeAndState((int) (2 * radius * 1.4f), heightMeasureSpec, 0)); } private void stopAnimation() { this.clearAnimation(); postInvalidate(); } private void startAnimation() { wa = new MoveAnimation(); wa.setDuration(2500); wa.setInterpolator(new AccelerateDecelerateInterpolator()); wa.setRepeatCount(Animation.INFINITE); wa.setRepeatMode(Animation.REVERSE); startAnimation(wa); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (visibility == GONE || visibility == INVISIBLE) { stopAnimation(); } else { startAnimation(); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); startAnimation(); } @Override protected void onDetachedFromWindow() { stopAnimation(); super.onDetachedFromWindow(); } }
文章来源《IT蓝豹》www.itlanbao.com