小六边形组成的加载动画
先分析一下:
注释都在代码里了:
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.CornerPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.View; import java.util.ArrayList; import java.util.List; /** * 六边形的加载动画 * 中间对应着代码中的最后一个,最右边的为第一个,逆时针依次 */ public class LoadingHexagon extends View { private Context context; /** * 六边形之间的间距值 */ private float space; /** * 每个小六边形的半径 */ private float hexagonRadius; private Paint paint; /** * 存放7个六边形的中点坐标 */ private List<HexagonPoint> centrePointList = new ArrayList<>(); /** * 渐显的状态 false * 渐暗的状态 true */ private boolean animationStart = false; //当前执行动画的索引 private int currentPoint = 0; /** * 控制动画 */ private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(animationStart){ //慢慢都显示 if(centrePointList.get(currentPoint).scale == 1){ currentPoint ++; } //Log.e("tag","逐渐显示" + currentPoint); }else{ //逐渐隐藏 if(centrePointList.get(currentPoint).scale == 0){ currentPoint ++; } //Log.e("tag","逐渐隐藏" + currentPoint); } if(currentPoint == centrePointList.size()){ animationStart = !animationStart; currentPoint = 0; //Log.e("tag","转换状态"); } flush(); handler.sendEmptyMessageDelayed(0,animDelayedTime); } }; /** * 一个小六边形一个变化的时间 */ private int animDelayedTime = 15; /** * 动画是否在执行 */ private boolean isExecute = false; public LoadingHexagon(Context context) { this(context,null); } public LoadingHexagon(Context context, AttributeSet attrs) { this(context, attrs,0); } public LoadingHexagon(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; initData(); } private void initData() { space = dp2px(context,1);//水平与垂直间隙为1dp hexagonRadius = dp2px(context,15);//半径 paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); paint.setStrokeWidth(3); paint.setColor(Color.parseColor("#6699ff")); //圆角处理 paint.setPathEffect(new CornerPathEffect(hexagonRadius * 0.1f)); initCentrePoint();//确定中心坐标 initPeakPoint();//确定顶点坐标 } /** * 确定7个六边形的中心坐标(没有缩放时的坐标) * 最右边为第一个,后面的按顺时针方向增加 */ private void initCentrePoint() { //大半径R,每个小六边形中心点到原点的距离 = 2 * r * cos(30度) + 水平间距 float radius = (float) (2 * hexagonRadius*Math.cos(2*Math.PI/360*30) + space); centrePointList.removeAll(centrePointList); for (int i=0;i<6;i++){ centrePointList.add(new HexagonPoint(getCosX(60*i,radius),getSinY(60*i,radius))); } //最后一个原点坐标 centrePointList.add(new HexagonPoint(0,0)); } /** * 确定每个小六边形的顶点坐标 * 因为每个小六边形的半径都是相等的,并且相对中心点来说与顶点的关系都是一样的 * 所以中心坐标加上对应的值就可以了 */ private void initPeakPoint() { //外围六个 for (int i=0;i<6;i++){ HexagonPoint hexagonPoint = centrePointList.get(i); //确定对应的六个顶点坐标 hexagonPoint.peakPointList.removeAll(hexagonPoint.peakPointList); for(int j=0;j<6;j++){ float x = hexagonPoint.point_x + getCosX(30+j*60,hexagonPoint.scale*hexagonRadius); float y = hexagonPoint.point_y + getSinY(30+j*60,hexagonPoint.scale*hexagonRadius); hexagonPoint.peakPointList.add(new PeakPoint(x,y)); } } //中间的小六边形的六个顶点 centrePointList.get(6).peakPointList.removeAll(centrePointList.get(6).peakPointList); for(int j=0;j<6;j++){ float x = 0 + getCosX(30+j*60,centrePointList.get(6).scale*hexagonRadius); float y = 0 + getSinY(30+j*60,centrePointList.get(6).scale*hexagonRadius); centrePointList.get(6).peakPointList.add(new PeakPoint(x,y)); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //平移坐标原点到中心位置 canvas.translate(getWidth()/2,getHeight()/2); initPeakPoint();//获取顶点坐标 Path path = new Path(); //画外围六个小六边形中心坐标组成的大六边形 /*path.moveTo(centrePointList.get(0).point_x,centrePointList.get(0).point_y); for (int i = 1; i < centrePointList.size()-1; i++) { path.lineTo(centrePointList.get(i).point_x,centrePointList.get(i).point_y); } //封闭 path.close(); paint.setColor(Color.parseColor("#ff0000")); paint.setStyle(Paint.Style.STROKE); canvas.drawPath(path,paint);*/ //画外围六个六边形 //paint.setColor(Color.parseColor("#6699ff")); //paint.setStyle(Paint.Style.FILL); for (int i = 0; i < centrePointList.size()-1; i++) { path.reset(); for (int j = 0; j < 6; j++) { if(j==0){ path.moveTo(centrePointList.get(i).peakPointList.get(j).point_x,centrePointList.get(i).peakPointList.get(j).point_y); }else{ path.lineTo(centrePointList.get(i).peakPointList.get(j).point_x,centrePointList.get(i).peakPointList.get(j).point_y); } } path.close(); paint.setAlpha(centrePointList.get(i).alpha); canvas.drawPath(path,paint); } //画在中心的六边形 path.reset(); for (int j = 0; j < 6; j++) { if(j == 0){ path.moveTo(centrePointList.get(6).peakPointList.get(j).point_x,centrePointList.get(6).peakPointList.get(j).point_y); }else{ path.lineTo(centrePointList.get(6).peakPointList.get(j).point_x,centrePointList.get(6).peakPointList.get(j).point_y); } } path.close(); paint.setAlpha(centrePointList.get(6).alpha); canvas.drawPath(path,paint); } /** * 刷新数据 */ private void flush() { if (animationStart) {//逐个显示出来 for (int i = 0; i < centrePointList.size(); i++) { if(currentPoint == i){ //在缩放的小六边形 centrePointList.get(i).addStart(); break; } } } else {//逐个消失 for (int i = 0; i < centrePointList.size(); i++) { if(currentPoint == i){ //在缩放的小六边形 centrePointList.get(i).subtractStart(); break; } } } invalidate(); } /** * 根据角度值获得X坐标 * @param angle 角度值(0-360) * @param radius 半径 * @return 该角度在X轴对应的坐标值 */ private float getCosX(int angle,float radius){ double jiaodu = (2*Math.PI / 360) * angle;//要偏移的角度,弧度与角度的转换 return (float) (radius*Math.cos(jiaodu)); } /** * 根据角度值获得Y坐标 * @param angle * @return */ private float getSinY(int angle,float radius){ double jiaodu = (2*Math.PI / 360) * angle; return (float) (radius*Math.sin(jiaodu)); } /** * dp转换为px */ public float dp2px(Context context , int dpVal){ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpVal,context.getResources().getDisplayMetrics()); } /** * 六边形的坐标点 */ private class HexagonPoint{ /** * X轴坐标 */ public float point_x; /** * Y轴坐标 */ public float point_y; /** * 缩放值 0-1f */ public float scale = 1; /** * 透明度 0-255 对应透明度的10次,所以每次步进值为25.5 */ public int alpha = 255; //透明度每次改变量 变化范围为[0,255] 这里分15次为一个周期 private int alpahChange = 26; private float scaleChange = 1.0f/10; /** * 存放每个小六边形对应的六个顶点坐标 */ public List<PeakPoint> peakPointList; public HexagonPoint(float point_x, float point_y) { this.point_x = point_x; this.point_y = point_y; peakPointList = new ArrayList<PeakPoint>(); } /** * 大小与透明度的增加 */ public void addStart(){ alpha = alpha + alpahChange; if(alpha >=255){ alpha = 255; } scale = scale + scaleChange; if(scale >= 1){ scale = 1; } } /** * 大小与透明度的减少 */ public void subtractStart(){ alpha = alpha - alpahChange; if(alpha <= 0){ alpha = 0; } scale = scale - scaleChange; if(scale <= 0){ scale = 0; } } } /** * 顶点坐标 */ private class PeakPoint{ /** * X轴坐标 */ public float point_x; /** * Y轴坐标 */ public float point_y; public PeakPoint(float point_x, float point_y) { this.point_x = point_x; this.point_y = point_y; } } /** * 停止动画 */ public void stopAnim(){ handler.removeMessages(0); isExecute = false;//记录状态 } /** * 重新开始动画 */ public void startAnim(){ if(!isExecute){ handler.removeMessages(0); isExecute = true; //接着暂停的状态开始 initCentrePoint(); initPeakPoint(); currentPoint = 0; handler.sendEmptyMessageDelayed(0,animDelayedTime); } } /** * 接着暂停的状态开始执行 */ public void continueAnim(){ if(!isExecute){ handler.removeMessages(0); isExecute = true; handler.sendEmptyMessageDelayed(0,animDelayedTime); } } /** * 设置小六边形的半径 * @param hexagonRadius */ public void setHexagonRadius(float hexagonRadius){ this.hexagonRadius = hexagonRadius; initCentrePoint(); invalidate(); } /** * 设置小六边形之间的间隙 * @param space 间隙值(水平方向与垂直方向的间隙相等) */ public void setHexagonSpace(float space){ this.space = space; initCentrePoint(); invalidate(); } /** * 设置小六边形的颜色 * @param color */ public void setHexagonColor(int color){ paint.setColor(color); invalidate(); } /** * 设置一个小六边形动画一个动作的时间 * @param animDelayedTime 默认为15ms */ public void setHexagonAnimTime(int animDelayedTime){ this.animDelayedTime = animDelayedTime; } }
使用:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical" tools:context="com.tsgithub.MainActivity"> <Button android:id="@+id/btn_start" android:text="重新开始" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_continue" android:text="接着暂停的状态开始" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_stop" android:text="暂停" android:layout_width="match_parent" android:layout_height="wrap_content" /> <com.tsgithub.view.LoadingHexagon android:id="@+id/loadingHexagon" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
public class MainActivity extends AppCompatActivity { private LoadingHexagon loadingHexagon; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadingHexagon = (LoadingHexagon) findViewById(R.id.loadingHexagon); //设置小六边形的颜色 间距 半径 /*loadingHexagon.setHexagonColor(Color.parseColor("#ffcccc")); loadingHexagon.setHexagonSpace(20); loadingHexagon.setHexagonRadius(100);*/ loadingHexagon.setHexagonAnimTime(10);//设置一个动画的时间,默认15 findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { loadingHexagon.startAnim(); } }); findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { loadingHexagon.stopAnim(); } }); findViewById(R.id.btn_continue).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { loadingHexagon.continueAnim(); } }); } @Override protected void onDestroy() { //需要停止动画 loadingHexagon.stopAnim(); super.onDestroy(); } }
效果图: