方式一
效果图:
simpleButton类代码:
package com.oneway.demo.navcontroller.view;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.oneway.demo.navcontroller.R;
public class SimpleLoadingButton extends View {
private int textColor;
private int backgroundNormal;
private float textSize;
private String content;
private Bitmap bitmap;
private int default_padding;
private int paddingBottom;
private int paddingLeft;
private int paddingTop;
private int paddingRight;
private Paint paintRect;
private Paint paintTxt;
private RectF rect;
private float txtHeight;
private Matrix matrix;
private ObjectAnimator animator;
private int mViewWidth;
private int mViewHeight;
private int STATE_NORMAL = 0;
private int STATE_LOADING = 1;
private int STATE_COMPLETED = 2;
private int state = STATE_NORMAL;
private String loadingTxt;
private String contentNormal;
private float corners;
private int backgroundPressed;
public SimpleLoadingButton(Context context) {
this(context, null, 0);
}
public SimpleLoadingButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SimpleLoadingButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initData(context);
}
/**
* 方法描述:初始化属性值
*
* @param context 上下文
* @param attrs 属性集合
*/
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.SimpleLoadingButton);
backgroundNormal = typeArray.getColor(R.styleable.SimpleLoadingButton_button_background_color_normal,
Color.parseColor("#3A96FF"));
backgroundPressed = typeArray.getColor(R.styleable.SimpleLoadingButton_button_background_color_pressed,
Color.parseColor("#1E90FF"));
// Log.e("Custom", "background===" + background);
// if (background >= TypedValue.TYPE_FIRST_COLOR_INT &&
// background <= TypedValue.TYPE_LAST_COLOR_INT){
textColor = typeArray.getColor(R.styleable.SimpleLoadingButton_button_text_color, Color.WHITE);
textSize = typeArray.getDimension(R.styleable.SimpleLoadingButton_button_text_size, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
contentNormal = typeArray.getString(R.styleable.SimpleLoadingButton_button_text);
loadingTxt = typeArray.getString(R.styleable.SimpleLoadingButton_button_loading_text);
corners = typeArray.getDimension(R.styleable.SimpleLoadingButton_corners, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 6, getResources().getDisplayMetrics()));
typeArray.recycle();
}
private void initData(Context context) {
if (contentNormal != null) {
content = contentNormal;
} else {
content = "";
}
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.loading);
default_padding = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 22, getResources().getDisplayMetrics());
paddingBottom = getPaddingBottom();
paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
//矩形画笔
paintRect = new Paint();
paintRect.setColor(backgroundNormal);
paintRect.setAntiAlias(true);
paintRect.setStyle(Paint.Style.FILL);
//矩形对象
rect = new RectF();
//文字画笔
paintTxt = new Paint();
paintTxt.setColor(textColor);
paintRect.setAntiAlias(true);
paintTxt.setTextAlign(Paint.Align.CENTER);
paintTxt.setTextSize(textSize);
txtHeight = (float) getTxtHeight(paintTxt);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = measureWidth(widthMeasureSpec);
int measureHeight = measureHeight(heightMeasureSpec);
initRect(measureWidth, measureHeight);
setMeasuredDimension(measureWidth, measureHeight);
}
/**
* 方法描述:矩形对象初始化
*/
private void initRect(int measureWidth, int measureHeight) {
rect.left = 0;
rect.top = 0;
rect.right = measureWidth;
rect.bottom = measureHeight;
}
private int measureWidth(int measureSpec) {
int width;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
width = specSize;
} else {
width = (int) paintTxt.measureText(content) + paddingLeft + paddingRight;
if (specMode == MeasureSpec.AT_MOST) {
width = Math.min(width, specSize);
}
}
return width;
}
private int measureHeight(int measureSpec) {
int height = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
height = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
height = (int) txtHeight + paddingTop + paddingBottom + default_padding;
}
return height;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRoundRect(rect, corners, corners, paintRect);
if (state == STATE_NORMAL) {
canvas.drawText(content, paddingLeft + rect.width() / 2 - paddingRight,
rect.height() / 2 + txtHeight / 3, paintTxt);
}
if (state == STATE_LOADING) {
canvas.translate(mViewWidth / 2, mViewHeight / 2);// 旋转的位置
matrix.preTranslate(-bitmap.getWidth() / 2, -bitmap.getHeight() / 2);//旋转中心点
canvas.drawBitmap(bitmap, matrix, null);
}
if (state == STATE_COMPLETED) {
canvas.drawText(content, paddingLeft + rect.width() / 2 - paddingRight,
rect.height() / 2 + txtHeight / 3, paintTxt);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (listener == null) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (loadingTxt == null) {
content = "";
} else {
content = loadingTxt;
}
paintRect.setColor(backgroundPressed);
state = STATE_NORMAL;
postInvalidate();
break;
case MotionEvent.ACTION_UP:
paintRect.setColor(backgroundNormal);
startAnim();
state = STATE_LOADING;
postInvalidate();
listener.onLoadingClick(this);
break;
}
return true;
}
/**
* 方法描述:加载完成以后让SimpleLoadingButton复位
*/
public void setCompleted() {
content = contentNormal;
state = STATE_COMPLETED;
cancelAnim();
postInvalidate();
}
/**
* 方法描述:获取文本字符的高度
*
* @param mPaint 画文本字符的画笔
* @return 文本字符的高度
*/
public double getTxtHeight(Paint mPaint) {
Paint.FontMetrics fm = mPaint.getFontMetrics();
return Math.ceil(fm.descent - fm.ascent);
}
/**
* 方法描述:开启动画
*/
private void startAnim() {
if (animator == null) {
initAnimator();
animator.start();
return;
}
if (!animator.isRunning()) {
animator.start();
}
}
/**
* 方法描述:取消动画
*/
private void cancelAnim() {
if (animator != null && animator.isRunning()) {
animator.cancel();
}
}
@SuppressLint("ObjectAnimatorBinding")
private void initAnimator() {
matrix = new Matrix();
animator = ObjectAnimator.ofFloat(matrix, "rotation", 0, 360);
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
matrix.setRotate((float) animation.getAnimatedValue());
invalidate();
}
});
}
private LoadingListener listener;
public void setLoadingListener(LoadingListener loginClickListener) {
this.listener = loginClickListener;
}
public interface LoadingListener {
void onLoadingClick(SimpleLoadingButton view);
}
}
value文件夹里面的attrs.xml代码为:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SimpleLoadingButton">
<attr name="button_background_color_normal" format="color" />
<attr name="button_background_color_pressed" format="color" />
<attr name="button_text" format="string" />
<attr name="button_text_color" format="color" />
<attr name="button_text_size" format="dimension" />
<attr name="button_loading_text" format="string" />
<attr name="corners" format="dimension" />
</declare-styleable>
</resources>
loading.png图标为:
在activity的xml里面添加代码:
<com.oneway.demo.navcontroller.view.SimpleLoadingButton
android:id="@+id/simple_loading_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:button_background_color_normal="#3A96FF"
app:button_background_color_pressed="#1E90FF"
app:button_loading_text="登录中..."
app:button_text="登录"
app:button_text_color="#ffffff"
app:button_text_size="18sp"
app:corners="8dp"
tools:ignore="MissingConstraints" />
最后,开始调用findviewbyid拿到SimpleLoadingButton控件,调用setLoadingListener方法
加载结束调用setCompleted方法复位
方式二
点击前效果:
点击后效果:
LoadingButton代码:
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
/**
*
*/
public class LoadingButton extends androidx.appcompat.widget.AppCompatButton {
private Context context;
// 开始Loading时的回调
private OnStartListener startListener;
// 结束Loading时的回调
private OnFinishListener finishListener;
// 开始和结束Loading时的回调
private OnLoadingListener listener;
// Loading动画旋转周期
private int rotateDuration = 1000;
// 按钮缩成Loading动画的时间
private int reduceDuration = 350;
// Loading旋转动画控制器
private Interpolator rotateInterpolator;
// 按钮缩成Loading动画的控制器
private Interpolator reduceInterpolator;
private int width;
private int height;
private String text;
// 是否在Loading中
private boolean isLoading = false;
public LoadingButton(Context context) {
this(context, null);
}
public LoadingButton(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public LoadingButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
this.context = context;
setGravity(Gravity.CENTER);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (width == 0) width = getMeasuredWidth();
if (height == 0) height = getMeasuredHeight();
}
/**
* 播放按钮缩成Loading的动画
*/
private void showStartLoadAnimation() {
ValueAnimator animator = new ValueAnimator().ofInt(width, height);
animator.setDuration(reduceDuration);
if (reduceInterpolator != null) animator.setInterpolator(reduceInterpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
getLayoutParams().width = (int) animation.getAnimatedValue();
requestLayout();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.button_main_color_selector));
setEnabled(false);
text = getText().toString();
setText("");
}
@Override
public void onAnimationEnd(Animator animation) {
showLoadingAnimation();
}
});
animator.start();
}
/**
* 播放Loading动画
*/
private void showLoadingAnimation() {
RotateAnimation animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(rotateDuration);
animation.setInterpolator(rotateInterpolator != null ? rotateInterpolator : new LinearInterpolator());
animation.setRepeatCount(-1);
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.circle_loading));
if (startListener != null) {
startListener.onStart();
} else if (listener != null) {
listener.onStart();
}
startAnimation(animation);
isLoading = true;
}
/**
* 播放Loading拉伸成按钮的动画
*/
private void showFinishLoadAnimation() {
ValueAnimator animator = new ValueAnimator().ofInt(height, width);
animator.setDuration(reduceDuration);
if (reduceInterpolator != null) animator.setInterpolator(reduceInterpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
getLayoutParams().width = (int) animation.getAnimatedValue();
requestLayout();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setBackgroundDrawable(context.getResources().getDrawable(R.drawable.button_main_color_selector));
setEnabled(false);
}
@Override
public void onAnimationEnd(Animator animation) {
setText(text);
setEnabled(true);
if (finishListener != null) {
finishListener.onFinish();
} else if (listener != null) {
listener.onFinish();
}
}
});
animator.start();
isLoading = false;
}
/**
* 开始Loading
*/
public void startLoading() {
if (!isLoading) {
clearAnimation();
showStartLoadAnimation();
}
}
/**
* 开始Loading
*
* @param listener Loading开始时的回调
*/
public void startLoading(OnStartListener listener) {
if (!isLoading) {
this.startListener = listener;
clearAnimation();
showStartLoadAnimation();
}
}
/**
* 结束Loading
*/
public void finishLoading() {
if (isLoading) {
clearAnimation();
showFinishLoadAnimation();
}
}
/**
* 结束Loading
*
* @param listener Loading结束时的回调
*/
public void finishLoading(OnFinishListener listener) {
if (isLoading) {
this.finishListener = listener;
clearAnimation();
showFinishLoadAnimation();
}
}
/**
* 设置Loading开始和结束时的回调接口
*
* @param listener
*/
public void setOnLoadingListener(OnLoadingListener listener) {
this.listener = listener;
}
/**
* 设置按钮缩成Loading动画的时间
*
* @param reduceDuration 时间,单位毫秒
*/
public void setReduceDuration(int reduceDuration) {
this.reduceDuration = reduceDuration;
}
/**
* 设置Loading动画旋转周期
*
* @param rotateDuration 旋转周期,单位毫秒
*/
public void setRotateDuration(int rotateDuration) {
this.rotateDuration = rotateDuration;
}
/**
* 获取是否正在Loading
*
* @return
*/
public boolean isLoading() {
return isLoading;
}
/**
* 设置Loading旋转动画控制器
*
* @param rotateInterpolator
*/
public void setRotateInterpolator(Interpolator rotateInterpolator) {
this.rotateInterpolator = rotateInterpolator;
}
/**
* 按钮缩成Loading动画的控制器
*
* @param reduceInterpolator
*/
public void setReduceInterpolator(Interpolator reduceInterpolator) {
this.reduceInterpolator = reduceInterpolator;
}
/**
* Loading开始时的回调接口
*/
public interface OnStartListener {
void onStart();
}
/**
* Loading结束时的回调接口
*/
public interface OnFinishListener {
void onFinish();
}
/**
* Loading开始和结束时的回调接口
*/
public interface OnLoadingListener {
void onStart();
void onFinish();
}
}
button_main_color_up.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<solid android:color="#45a8ca" />
<gradient
android:endColor="#45a8ca"
android:startColor="#6fd1e7" />
</shape>
button_main_color_down.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp" />
<solid android:color="#45a8aa" />
<gradient
android:endColor="#45a8aa"
android:startColor="#6fd1c7" />
</shape>
button_main_color_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_main_color_up" android:state_pressed="false"></item>
<item android:drawable="@drawable/button_main_color_down" android:state_pressed="true"></item>
</selector>
circle_loading.xml代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:innerRadius="18dp"
android:shape="ring"
android:thickness="2dp"
android:useLevel="false">
<solid android:color="#45a8ca" />
<gradient
android:endColor="#45a8ca"
android:startColor="#0000"
android:type="sweep" />
</shape>
在activity.xml里面的代码为:
<com.oneway.demo.loadingbutton.LoadingButton
android:id="@+id/load_btn"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="10dp"
android:background="@drawable/button_main_color_selector"
android:text="CLICK"
android:textColor="#ffffff"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
在activity里的代码为(开始加载):
findViewById<LoadingButton>(R.id.load_btn).setOnClickListener {
(it as LoadingButton).startLoading()
}
结束加载:finishLoading
方式三
效果图:
LoadingButton的代码
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
/**
* 封装的LoadingButton按钮
*
* @author WY+ 24/9/19.
*/
class LoadingButton(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) {
private var title: String
private var tvTitle: TextView
// 是否在Loading中
private var isLoading = false
private var view: View
private var progressBar: ProgressBar? = null
private var timeoutCallBack: (Int) -> Unit = {}
private val handler = Handler(Looper.getMainLooper())
private val runnable = Runnable {
if (isLoading) {
hideLoading()
// 在这里处理超时逻辑,例如显示提示信息
timeoutCallBack.invoke(1)
}
}
private var timeout: Long = 20000L
init {
// 自定义TitleLayout的相关属性
view = LayoutInflater.from(context).inflate(R.layout.layout_loading_button, this, true)
progressBar = view.findViewById<ProgressBar>(R.id.pb_loading)
tvTitle = view.findViewById<TextView>(R.id.tv_loading)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingButton)
title = typedArray.getString(R.styleable.LoadingButton_text) ?: ""
typedArray.recycle()
applyValue()
}
private fun applyValue() {
tvTitle.text = title
}
/**
* 重写点击事件
*/
override fun setOnClickListener(l: OnClickListener?) {
super.setOnClickListener {
if (!isLoading) {
l?.onClick(it)
showLoading()
handler.postDelayed(runnable, timeout) // 设置20秒后执行runnable
}
// else {
// hideLoading()
// }
}
}
private fun showLoading() {
isLoading = true
progressBar?.visibility = View.VISIBLE
}
public fun hideLoading() {
isLoading = false
progressBar?.visibility = View.GONE
handler.removeCallbacks(runnable) // 移除定时任务
}
/**
* 外部调用,设置Loading超时时间
*/
public fun setLoadingTimeout(timeout: Long) {
this.timeout = timeout
}
fun timeoutBack(callBack: (Int) -> Unit = {}): LoadingButton {
timeoutCallBack = callBack
return this
}
/**
* 外部调用,获取当前Loading状态
*/
public fun isLoading(): Boolean {
return isLoading
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
handler.removeCallbacksAndMessages(null)
}
}
layout_loading_button.xml代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_loading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="4dp"
android:indeterminateDrawable="@drawable/waitingcircle"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tv_loading"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/tv_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/pb_loading"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
使用:第一步在xml里面添加(跟普通的控件使用方式一样)
<com.yaona.loadingbutton.LoadingButton
android:id="@+id/lb_login"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:background="@drawable/shape_round_main_12"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:text="Hello World!" />
activity里面调用点击事件:
val mLoadingButton = findViewById<LoadingButton>(R.id.lb_login)
mLoadingButton.setOnClickListener {
Log.e("wang", "点击==开始异步网络请求")
Handler(Looper.myLooper()!!).postDelayed({
Log.e("wang", "拿到请求结果")
mLoadingButton.complete()
}, 3000)
}
shape_round_main_12.xml代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<layer-list>
<item android:bottom="4dp" android:left="4dp" android:right="4dp" android:top="4dp">
<shape>
<solid android:color="#03A9F4" />
<corners android:radius="24dp" />
</shape>
</item>
</layer-list>
</item>
<item android:state_pressed="false">
<layer-list>
<item>
<shape>
<solid android:color="#03A9F4" />
<corners android:radius="24dp" />
</shape>
</item>
</layer-list>
</item>
</selector>
waitingcircle.xml的代码
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/circle_run"
android:fromDegrees="0.0"
android:pivotX="50.0%"
android:pivotY="50.0%"
android:toDegrees="360.0" />
waitingcircle.xml的代码
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M6,13c-2.2,0 -4,1.8 -4,4s1.8,4 4,4s4,-1.8 4,-4S8.2,13 6,13zM12,3C9.8,3 8,4.8 8,7s1.8,4 4,4s4,-1.8 4,-4S14.2,3 12,3zM18,13c-2.2,0 -4,1.8 -4,4s1.8,4 4,4s4,-1.8 4,-4S20.2,13 18,13z"/>
</vector>