Commit 8f9a78d5 authored by mengcuiguang's avatar mengcuiguang

代码优化

parent 1af1a0f7
package com.mints.wisdomclean.ui.adapter;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.mints.library.utils.Utils;
import com.mints.wisdomclean.R;
import com.mints.wisdomclean.mvp.model.Duplicate;
import com.mints.wisdomclean.mvp.model.TypeFile;
import java.io.File;
import java.util.ArrayList;
/**
* Created by sonu on 24/07/17.
*/
public class ItemRecyclerViewAdapter extends RecyclerView.Adapter<ItemRecyclerViewAdapter.ItemViewHolder> {
class ItemViewHolder extends RecyclerView.ViewHolder {
private TextView itemLabel;
private TextView tvSize;
private TextView tvPath;
ImageView image,ivPlay;
AppCompatCheckBox ivCheckbox;
private RelativeLayout rlCard;
public ItemViewHolder(View itemView) {
super(itemView);
itemLabel = (TextView) itemView.findViewById(R.id.name);
tvSize = (TextView) itemView.findViewById(R.id.size);
tvPath = (TextView) itemView.findViewById(R.id.path);
image = (ImageView) itemView.findViewById(R.id.image);
ivCheckbox = (AppCompatCheckBox)itemView.findViewById(R.id.checked);
ivPlay = (ImageView) itemView.findViewById(R.id.play);
rlCard = (RelativeLayout) itemView.findViewById(R.id.rlCard);
}
}
private Context context;
private ArrayList<Duplicate> mDuplicates;
public ItemRecyclerViewAdapter(Context context, ArrayList<Duplicate> arrayList) {
this.context = context;
this.mDuplicates = arrayList;
}
@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_custom_row_dupicate, parent, false);
return new ItemViewHolder(view);
}
@Override
public void onBindViewHolder(final ItemViewHolder holder, final int position) {
final Duplicate mDuplicate = mDuplicates.get(position);
holder.itemLabel.setText(getFileName(mDuplicate.getFile().getPath()));
holder.tvSize.setText(Utils.formatSize(mDuplicate.getFile().length()));
holder.tvPath.setText(mDuplicate.getFile().getPath());
holder.ivCheckbox.setChecked(mDuplicate.isChecked());
switch (mDuplicate.getTypeFile()){
case TypeFile.IMAGE:
try {
Glide.with(context)
.load("file://" + mDuplicate.getFile().getPath())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.VIDEO:
holder.ivPlay.setVisibility(View.VISIBLE);
try {
Glide.with(context)
.load("file://" + mDuplicate.getFile().getPath())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.DOCUMENT:
try {
Glide.with(context)
.load(R.drawable.document)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.PDF:
try {
Glide.with(context)
.load(R.drawable.ic_pdf)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.ZIP:
try {
Glide.with(context)
.load(R.drawable.zip)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.APK:
try {
Glide.with(context)
.load(R.drawable.android)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.VCF:
try {
Glide.with(context)
.load(R.drawable.vcf)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
case TypeFile.AUDIO:
try {
Glide.with(context)
.load(R.drawable.audio)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
default:
try {
Glide.with(context)
.load(R.drawable.unknown)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
.centerCrop()
.error(R.mipmap.ic_launcher_main)
.into(holder.image);
} catch (Exception e){
//do nothing
Toast.makeText(context, "Exception: "+e.getMessage(), Toast.LENGTH_SHORT).show();
}
break;
}
holder.ivCheckbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(holder.ivCheckbox.isChecked()){
mDuplicate.setChecked(true);
}else{
mDuplicate.setChecked(false);
}
// Toast.makeText(context,"Check checkbox ne",Toast.LENGTH_LONG).show();
}
});
holder.rlCard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
openFile(mDuplicate);
}catch (Exception e){
}
}
});
}
public void openFile(Duplicate mDuplicate){
Intent createChooser;
try {
File file = mDuplicate.getFile();
if (mDuplicate.getTypeFile()==1) {
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
if (file.exists()) {
if (Build.VERSION.SDK_INT < 24) {
intent.setDataAndType(Uri.fromFile(file), "audio/*");
} else {
Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
context.grantUriPermission(context.getPackageName(), contentUri, 1);
intent.setDataAndType(contentUri, "audio/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
createChooser = Intent.createChooser(intent, "Complete action using");
context.startActivity(createChooser);
}
return;
} else if (mDuplicate.getTypeFile()==2) {
if(Build.VERSION.SDK_INT<24){
Intent intent2 = new Intent("android.intent.action.VIEW");
intent2.setDataAndType(Uri.fromFile(mDuplicate.getFile()), "video/*");
createChooser = Intent.createChooser(intent2, "Complete action using");
}else{
Intent intent4 = new Intent("android.intent.action.VIEW");
Uri contentUri2 = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
context.grantUriPermission(context.getPackageName(), contentUri2, 1);
intent4.setType("*/*");
if (Build.VERSION.SDK_INT < 24) {
contentUri2 = Uri.fromFile(file);
}
intent4.setData(contentUri2);
intent4.setFlags(1);
createChooser = Intent.createChooser(intent4, "Complete action using");
}
} else if (Build.VERSION.SDK_INT < 24) {
Uri fromFile = Uri.fromFile(file);
Intent intent3 = new Intent("android.intent.action.VIEW");
String str = "*/*";
MimeTypeMap singleton = MimeTypeMap.getSingleton();
if (singleton.hasExtension(MimeTypeMap.getFileExtensionFromUrl(fromFile.toString()))) {
str = singleton.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(fromFile.toString()));
}
intent3.setDataAndType(fromFile, str);
context.startActivity(intent3);
return;
} else {
Intent intent4 = new Intent("android.intent.action.VIEW");
Uri contentUri2 = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
context.grantUriPermission(context.getPackageName(), contentUri2, 1);
intent4.setType("*/*");
if (Build.VERSION.SDK_INT < 24) {
contentUri2 = Uri.fromFile(file);
}
intent4.setData(contentUri2);
intent4.setFlags(1);
createChooser = Intent.createChooser(intent4, "Complete action using");
}
context.startActivity(createChooser);
} catch (Exception e) {
}
}
// public void openFile(File url) {
//
// Uri uri = Uri.fromFile(url);
// String fileName = url.getName();
// Intent intent = new Intent(Intent.ACTION_VIEW);
// Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", url);
// context.grantUriPermission(context.getPackageName(), contentUri, 1);
//// if (fileName.endsWith(".doc") || fileName.endsWith(".docx")) {
//// // Word document
//// intent.setDataAndType(uri, "application/msword");
//// } else if (fileName.endsWith(".pdf")) {
//// // PDF file
//// intent.setDataAndType(uri, "application/pdf");
//// } else if (fileName.endsWith(".ppt") || fileName.endsWith(".pptx")) {
//// // Powerpoint file
//// intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
//// } else if (fileName.endsWith(".xls") || fileName.endsWith(".xlsx")) {
//// // Excel file
//// intent.setDataAndType(uri, "application/vnd.ms-excel");
//// } else if (fileName.endsWith(".zip") || fileName.endsWith(".rar")) {
//// // WAV audio file
//// intent.setDataAndType(uri, "application/x-wav");
//// } else if (fileName.endsWith(".rtf")) {
//// // RTF file
//// intent.setDataAndType(uri, "application/rtf");
//// } else if (fileName.endsWith(".wav") || fileName.endsWith(".mp3")) {
//// // WAV audio file
//// intent.setDataAndType(uri, "audio/x-wav");
//// } else if (fileName.endsWith(".gif")) {
//// // GIF file
//// intent.setDataAndType(uri, "image/gif");
//// } else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||fileName.endsWith(".png")) {
//// // JPG file
//// intent.setDataAndType(uri, "image/jpeg");
//// } else if (fileName.endsWith(".txt")) {
//// // Text file
//// intent.setDataAndType(uri, "text/plain");
//// } else if (fileName.endsWith(".3gp") || fileName.endsWith(".mpg") || fileName.endsWith(".mpeg") || fileName.endsWith(".mpe") || fileName.endsWith(".mp4") || fileName.endsWith(".avi")) {
//// // Video files
//// intent.setDataAndType(uri, "video/*");
//// } else if (fileName.endsWith(".apk")) {
//// // GIF file
//// intent.setDataAndType(uri, "application/vnd.android.package-archive");
//// }else {
////
//// //if you want you can also define the intent type for any other file
//// //additionally use else clause below, to manage other unknown extensions
//// //in this case, Android will show all applications installed on the device
//// //so you can choose which application to use
//// intent.setDataAndType(uri, "*/*");
//// }
// intent.setDataAndType(uri, TypeOpen.getType(url));
// intent.setData(contentUri);
// intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// context.startActivity(Intent.createChooser(intent, "Complete action using"));
//
// }
public String getFileName(String path) {
String filename=path.substring(path.lastIndexOf("/")+1);
return filename;
}
@Override
public int getItemCount() {
return mDuplicates.size();
}
}
package com.mints.wisdomclean.ui.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.mints.wisdomclean.R;
import com.mints.wisdomclean.mvp.model.DataModel;
import com.mints.wisdomclean.ui.widgets.RecyclerViewType;
import java.util.ArrayList;
/**
* Created by sonu on 24/07/17.
*/
public class SectionRecyclerViewAdapter extends RecyclerView.Adapter<SectionRecyclerViewAdapter.SectionViewHolder> {
class SectionViewHolder extends RecyclerView.ViewHolder {
private TextView sectionLabel, showAllButton;
private RecyclerView itemRecyclerView;
public SectionViewHolder(View itemView) {
super(itemView);
sectionLabel = (TextView) itemView.findViewById(R.id.section_label);
itemRecyclerView = (RecyclerView) itemView.findViewById(R.id.item_recycler_view);
}
}
private Context context;
private RecyclerViewType recyclerViewType;
private ArrayList<DataModel> sectionModelArrayList;
public SectionRecyclerViewAdapter(Context context, RecyclerViewType recyclerViewType, ArrayList<DataModel> sectionModelArrayList) {
this.context = context;
this.recyclerViewType = recyclerViewType;
this.sectionModelArrayList = sectionModelArrayList;
}
@Override
public SectionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.section_custom_row_dupicate, parent, false);
return new SectionViewHolder(view);
}
@Override
public void onBindViewHolder(SectionViewHolder holder, int position) {
final DataModel mDataModel = sectionModelArrayList.get(position);
holder.sectionLabel.setText(mDataModel.getTitleGroup());
//recycler view for items
holder.itemRecyclerView.setHasFixedSize(true);
holder.itemRecyclerView.setNestedScrollingEnabled(false);
/* set layout manager on basis of recyclerview enum type */
switch (recyclerViewType) {
case LINEAR_VERTICAL:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, RecyclerView.VERTICAL, false);
holder.itemRecyclerView.setLayoutManager(linearLayoutManager);
break;
case LINEAR_HORIZONTAL:
LinearLayoutManager linearLayoutManager1 = new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false);
holder.itemRecyclerView.setLayoutManager(linearLayoutManager1);
break;
case GRID:
GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 3);
holder.itemRecyclerView.setLayoutManager(gridLayoutManager);
break;
}
ItemRecyclerViewAdapter adapter = new ItemRecyclerViewAdapter(context, mDataModel.getListDuplicate());
holder.itemRecyclerView.setAdapter(adapter);
//show toast on click of show all button
// holder.showAllButton.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// Toast.makeText(context, "You clicked on Show All of : " + mDataModel.getTitleGroup() ,Toast.LENGTH_SHORT).show();
// }
// });
}
@Override
public int getItemCount() {
return sectionModelArrayList.size();
}
}
package com.mints.wisdomclean.ui.widgets
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.mints.wisdomclean.R
class BgChooseView : View {
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
companion object {
const val BG_COLOR_RED = "#FF0000"
const val BG_COLOR_BLUE = "#00BFF3"
const val BG_COLOR_WHITE = "#FFFFFF"
const val BG_COLOR_BLUE_GRADUAL = "BLUE_GRADUAL"
}
private var mColor: String = BG_COLOR_BLUE
private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mIsChecked: Boolean = false
private val mGary = context.resources.getColor(R.color.id_photo_gray)
private var mWidth = 0f
private var mHeight = 0f
init {
mPaint.strokeWidth = 6f
}
fun setColor(color: String) {
mColor = color
invalidate()
}
fun setChecked(isChecked: Boolean) {
mIsChecked = isChecked
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mPaint.shader = null
mPaint.color = mGary
mPaint.style = Paint.Style.FILL
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint)
if (mColor == BG_COLOR_BLUE_GRADUAL) {
val colors = IntArray(2)
val positions = FloatArray(2)
// 第1个点
colors[0] = Color.parseColor(BG_COLOR_BLUE)
positions[0] = 0f
// 第2个点
colors[1] = Color.WHITE
positions[1] = 1f
val shader = LinearGradient(
0f, 0f,
0f, mHeight,
colors,
positions,
Shader.TileMode.MIRROR)
mPaint.shader = shader
} else {
mPaint.shader = null
mPaint.color = Color.parseColor(mColor)
mPaint.style = Paint.Style.FILL
}
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - 12, mPaint)
if (mIsChecked) {
mPaint.style = Paint.Style.STROKE
mPaint.color = Color.WHITE
if (mColor == BG_COLOR_WHITE) {
mPaint.color = mGary
}
canvas.translate(mWidth / 2 - 25, mHeight / 2)
val path = Path()
path.lineTo(25f, 20f)
path.lineTo(50f, -20f)
canvas.drawPath(path, mPaint)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val minimumWidth = suggestedMinimumWidth
val minimumHeight = suggestedMinimumHeight
val width = measureWidth(minimumWidth, widthMeasureSpec)
val height = measureHeight(minimumHeight, heightMeasureSpec)
mWidth = width.toFloat()
mHeight = height.toFloat()
setMeasuredDimension(width, height)
}
private fun measureWidth(defaultWidth: Int, measureSpec: Int): Int {
var measureWidth = defaultWidth
val specMode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)
when (specMode) {
MeasureSpec.AT_MOST -> {
measureWidth = specSize + paddingLeft + paddingRight
}
MeasureSpec.EXACTLY -> {
measureWidth = specSize
}
MeasureSpec.UNSPECIFIED -> {
measureWidth = defaultWidth.coerceAtLeast(specSize)
}
}
return measureWidth
}
private fun measureHeight(defaultHeight: Int, measureSpec: Int): Int {
var measureHeight = defaultHeight
val specMode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)
when (specMode) {
MeasureSpec.AT_MOST -> {
measureHeight = specSize + paddingTop + paddingBottom
}
MeasureSpec.EXACTLY -> {
measureHeight = specSize
}
MeasureSpec.UNSPECIFIED -> {
measureHeight = defaultHeight.coerceAtLeast(specSize)
}
}
return measureHeight
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Property;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
import com.mints.wisdomclean.R;
/*
* findViewById返回Null..
* 自定义View需要重写两个参数的构造
* 并且调用super(context, attrs);方法
* 为什么调用三个的构造却不行。
* 事实证明调用三个的构造函数也是可以的 不过defStyleAttr必须设置为0 而不是-1;
*/
public class ButtonView extends View {
int mWidth=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, this.getResources().getDisplayMetrics());
int onBackground;
int offBackground;
int time;
float layout_width;
float layout_height;
Paint mPaint;
Scroller mScroller;
float lastX;
VelocityTracker mVelocity;
ViewConfiguration mConfig;
static final int RADIO=50;
int circleX=0;
boolean isOn=false;//是否开启了
AnimatorSet set;
int circleColor;
private Property<ButtonView, Integer> mColor=new Property<ButtonView, Integer>(Integer.class,"currentColor") {
@Override
public Integer get(ButtonView object) {
return object.currentColor;
}
@Override
public void set(ButtonView object, Integer value) {
// TODO Auto-generated method stub
object.currentColor=value;
invalidate();
}
};
private Property<ButtonView, Integer> mMove=new Property<ButtonView, Integer>(Integer.class,"current_move") {
@Override
public Integer get(ButtonView object) {
// TODO Auto-generated method stub
return object.current_move;
}
@Override
public void set(ButtonView object, Integer value) {
// TODO Auto-generated method stub
object.current_move=value;
}
};
boolean isAnimatorStart=false;
@SuppressWarnings("unchecked")
public void startAnimator(int start,int end,int startColor,int endColor,int duration){
set=new AnimatorSet();
set.playTogether(ObjectAnimator.ofInt(this,mMove, start,end),ObjectAnimator.ofObject(this, mColor, new ArgbEvaluator(),startColor,endColor));
set.setDuration(duration);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// TODO Auto-generated method stub
isAnimatorStart=true;
}
@Override
public void onAnimationEnd(Animator animation) {
isAnimatorStart=false;
if(changeListener!=null){
changeListener.onChange(isOn);
}
}
});
set.setInterpolator(new DecelerateInterpolator());
set.start();
}
public ButtonView(Context context) {
this(context,null);
}
public ButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public ButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.buttonView);
offBackground=a.getColor(R.styleable.buttonView_offBackground, Color.parseColor("#F6F6F6"));
onBackground=a.getColor(R.styleable.buttonView_onBackground, Color.parseColor("#38DA4E"));
time=a.getInteger(R.styleable.buttonView_time, 500);
circleColor=a.getColor(R.styleable.buttonView_circleColor, Color.parseColor("#D7D7D7"));
layout_height=a.getDimension(R.styleable.buttonView_layout_height, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics()));
layout_width=a.getDimension(R.styleable.buttonView_layout_width, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, context.getResources().getDisplayMetrics()));
mPaint=new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(offBackground);
mPaint.setDither(true);
mScroller=new Scroller(context);
mConfig=ViewConfiguration.get(context);
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
if(mScroller.computeScrollOffset()){
current_move=mScroller.getCurrX();
invalidate();
}
}
int min_move=circleX+RADIO;
int max_move=0;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode= MeasureSpec.getMode(heightMeasureSpec);
int heightSize= MeasureSpec.getSize(heightMeasureSpec);
int widthMode= MeasureSpec.getMode(widthMeasureSpec);
int widthSize= MeasureSpec.getSize(widthMeasureSpec);
int measureView_width = measureView(widthMode, widthSize, layout_width);
int measureView_height= measureView(heightMode, heightSize, layout_height);
//最大移动位置
max_move=(int) (layout_width-RADIO-circleX);
//最小移动位置
setMeasuredDimension(measureView_width, measureView_height);
}
private int measureView(int mode,int size,float defaultSize){
switch (mode) {
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
if(size>defaultSize){
size=(int) Math.ceil(defaultSize);
}
return MeasureSpec.makeMeasureSpec(size,MeasureSpec.EXACTLY);
case MeasureSpec.EXACTLY:
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
return -1;
}
int current_move=min_move;
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setColor(currentColor);
mPaint.setDither(true);
canvas.drawRoundRect(new RectF(0, 0, layout_width, layout_height), RADIO, RADIO, mPaint);
mPaint.setStyle(Style.STROKE);
mPaint.setColor(Color.parseColor("#DADADA"));
canvas.drawRoundRect(new RectF(0, 0, layout_width, layout_height), RADIO, RADIO, mPaint);
mPaint.setStyle(Style.FILL);
mPaint.setColor(circleColor);
canvas.drawCircle(current_move, layout_height/2, layout_height/2-4, mPaint);
}
float x=0;
float y=0;
boolean isClick=false;
Handler mHandler=new Handler();
/**
* 当前颜色
*/
int currentColor=offBackground;
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
mHandler.removeCallbacks(clickRunable);
if(isClick){
if(isOn){//开启则关闭
if(!isAnimatorStart){
startAnimator(max_move, min_move, onBackground, offBackground, time);
isOn=false;
}
// if(mScroller.isFinished()){
// mScroller.startScroll(max_move, (int)this.getY(), min_move-max_move,(int)this.getY(), 1000);
// currentColor=offBackground;
// invalidate();
// isOn=false;
// }
}else{//否则开启
if(!isAnimatorStart){
startAnimator(min_move, max_move, offBackground, onBackground, time);
isOn=true;
}
//if(mScroller.isFinished()){
// mScroller.startScroll(min_move, (int)this.getY(), max_move-min_move,(int)this.getY(), 1000);
// currentColor=onBackground;
// invalidate();
// isOn=true;
//}
}
if(click!=null){
click.onClick(this);
}
//Toast.makeText(getContext(), "单击事件", Toast.LENGTH_SHORT).show();
}
break;
case MotionEvent.ACTION_DOWN:
x=event.getX();
y=event.getY();
isClick=false;
mHandler.postDelayed(clickRunable, mConfig.getScaledTouchSlop());
break;
case MotionEvent.ACTION_MOVE:
float x=event.getX();
float y=event.getY();
isMove(x, y, mConfig.getScaledTouchSlop());
break;
default:
break;
}
return true;
}
private void isMove(float x,float y,int touchSlop){
if(Math.abs(this.x-x)>touchSlop||Math.abs(this.y-y)>touchSlop){
isClick=false;
}
}
private onChangeListener changeListener;
private onClickListener click;
public void setClick(onClickListener click) {
this.click = click;
}
public void setChangeListener(onChangeListener changeListener) {
this.changeListener = changeListener;
}
public interface onChangeListener{
void onChange(boolean state);
}
public interface onClickListener{
void onClick(View view);
}
ClickRunAble clickRunable=new ClickRunAble();
class ClickRunAble implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
isClick=true;
}
}
}
package com.mints.wisdomclean.ui.widgets;
import android.app.ProgressDialog;
import android.content.Context;
public class CustomProgressDialog extends ProgressDialog {
public CustomProgressDialog(Context context) {
super(context);
setMessage("压缩中...");
setMax(100);
setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
setCanceledOnTouchOutside(false);
setCancelable(false);
}
@Override
public void dismiss() {
super.dismiss();
setProgress(0);
}
@Override
public void cancel() {
super.cancel();
setProgress(0);
}
}
@file:Suppress("SpellCheckingInspection")
package com.mints.wisdomclean.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.graphics.*
import android.text.TextUtils
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import androidx.core.content.ContextCompat
import com.mints.wisdomclean.R
import kotlin.math.cos
import kotlin.math.sin
private val TAG = DashboradView::class.java.simpleName
/**
*
* @author jyx
* @date 2021/6/4
* @des 测速仪表盘
*/
class DashboradView : View {
/** 缺省值 */
private var mRadius = 0 // 扇形半径
private var mStartAngle = 150f // 起始角度,180-30度(左边平行夹角30度)
private var mSweepAngle = 240f // 绘制角度,360+30度 = 150+240度)
private var mMin = 0 // 最小值 0Mb/s
private var mMax = 100 // 最大值 1000Mb/s
private var mSection = 8 // 值域(mMax-mMin)等分份数
private var mPortion = 4 // 一个mSection等分份数
private var mHeaderText = "" // 表头 Mbs
private var mVelocity = mMin // 实时速度
private var mSpeed = mMin.toFloat() // 实时速度
private var mStrokeWidth = 0f // 画笔宽度
private var mLength1 = 0 // 长刻度的相对圆弧的长度
private var mLength2 = 0 // 刻度读数顶部的相对圆弧的长度
private var mPLRadius = 0 // 指针长半径
private var mPSRadius = 0 // 指针短半径
private var mPadding = 0
private var mCenterX = 0f // 坐标轴X
private var mCenterY = 0f // 坐标轴Y
private var mPaint: Paint
private var mRectFArc: RectF
private var mPath: Path
private var mRectFInnerArc: RectF
private var mRectText: Rect
private var mTexts: Array<String>
private var mReadSpeed = 0f // 真实进度
private var mReadSpeedStr = "KB/s" // 真实进度单位
// private var mColors: IntArray
private var mGradient: Shader? = null
private var mContext: Context = context
constructor(ctx: Context) : this(ctx, null)
constructor(ctx: Context, attrs: AttributeSet?) : this(ctx, attrs, 0)
constructor(ctx: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
ctx,
attrs,
defStyleAttr
)
init {
mStrokeWidth = dp2px(3).toFloat()
mLength1 = (dp2px(8) + mStrokeWidth).toInt()
mLength2 = mLength1 + dp2px(4)
mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.strokeCap = Paint.Cap.ROUND
mRectFArc = RectF()
mPath = Path()
mRectFInnerArc = RectF()
mRectText = Rect()
mTexts = arrayOf("0M", "10M", "20M", "50M", "100M", "200M", "500M", "800M", "1000M")
// for (i in 0..mSection + 1) {// 需要显示mSection + 1个刻度读数
// val n = (mMax - mMin) / mSection
// mTexts[i] = "" + (mMin + i * n)
// }
// mTexts[0] = "0M"
// mTexts[1] = "1M"
// mTexts[2] = "2M"
// mTexts[3] = "5M"
// mTexts[4] = "10M"
// mTexts[5] = "20M"
// mTexts[6] = "50M"
// mTexts[7] = "80M"
// mTexts[8] = "100M"
// mTexts[9] = "1000M"
// mColors = intArrayOf(
// ContextCompat.getColor(mContext, R.color.color_4BB93F),
// ContextCompat.getColor(mContext, R.color.color_fcf16e),
// ContextCompat.getColor(mContext, R.color.red)
// )
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mPadding = paddingLeft.coerceAtLeast(paddingTop)
.coerceAtLeast(paddingRight.coerceAtLeast(paddingBottom))
setPadding(mPadding, mPadding, mPadding, mPadding)
val width = resolveSize(dp2px(230), widthMeasureSpec)
mRadius = ((width - mPadding * 2 - mStrokeWidth * 2) / 2).toInt()
// 由起始角度确定的高度
val point1 = getCoordinatePoint(mRadius, mStartAngle)
// 由结束角度确定的高度
// val point2 = getCoordinatePoint(mRadius, mStartAngle + mSweepAngle)
// val height =
// (point1[1] + mRadius + mStrokeWidth * 2).coerceAtLeast(point2[1] + mRadius + mStrokeWidth * 2)
// .toInt()
// LogUtil.d(TAG, "mRadius -> " + mRadius + " mStrokeWidth -> " + mStrokeWidth)
// LogUtil.d(TAG, "point1 -> " + point1[1] + " point2 -> " + point2[1])
// LogUtil.d(TAG, "width -> " + width + " height -> " + height)
val height = (point1[1] + mStrokeWidth * 2 + 20).toInt()
setMeasuredDimension(width, height)
mCenterX = measuredWidth / 2f
mCenterY = measuredWidth / 2f
mRectFArc.set(
paddingLeft + mStrokeWidth,
paddingTop + mStrokeWidth,
measuredWidth - paddingRight - mStrokeWidth,
measuredWidth - paddingBottom - mStrokeWidth
)
mPaint.textSize = sp2px(14).toFloat()
mPaint.getTextBounds("0", 0, "0".length, mRectText)
mRectFInnerArc.set(
(paddingLeft + mLength2 + mRectText.height() + dp2px(30)).toFloat(),
(paddingTop + mLength2 + mRectText.height() + dp2px(30)).toFloat(),
(measuredWidth - paddingRight - mLength2 - mRectText.height() - dp2px(30)).toFloat(),
(measuredWidth - paddingBottom - mLength2 - mRectText.height() - dp2px(30)).toFloat()
)
mPLRadius = mRadius - dp2px(35)
mPSRadius = dp2px(25)
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
setLayerType(LAYER_TYPE_SOFTWARE, null);
/** 画测速仪背景色 */
// canvas.drawColor(mContext.getColor(R.color.color_dark))
/** 画圆弧 最外层 */
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = mStrokeWidth
mGradient = LinearGradient(
0f,
0f,
mRectFArc.right,
mRectFArc.bottom,
Color.parseColor("#8275DF"),
Color.parseColor("#25C7AD"),
Shader.TileMode.MIRROR
)
mPaint.shader = mGradient
canvas.drawArc(mRectFArc, mStartAngle, mSweepAngle, false, mPaint)
mPaint.shader = null
val mRectFArc1 = RectF(
mRectFArc.left + dp2px(2),
mRectFArc.top + dp2px(2),
mRectFArc.right - dp2px(2),
mRectFArc.bottom - dp2px(2)
)
mPaint.color = Color.parseColor("#E2E2E2")
canvas.drawArc(mRectFArc1, mStartAngle, mSweepAngle, false, mPaint)
/**
* 画长刻度
* 画好起始角度的一条刻度后通过canvas绕着原点旋转来画剩下的长刻度
*/
val cos = cos(Math.toRadians((mStartAngle - 180).toDouble()))
val sin = sin(Math.toRadians((mStartAngle - 180).toDouble()))
val x0 = (mPadding + mStrokeWidth + mRadius * (1 - cos)).toFloat()
val y0 = (mPadding + mStrokeWidth + mRadius * (1 - sin)).toFloat()
val x1 = (mPadding + mStrokeWidth + mRadius - (mRadius - mLength1) * cos).toFloat()
val y1 = (mPadding + mStrokeWidth + mRadius - (mRadius - mLength1) * sin).toFloat()
canvas.save()
mPaint.color = Color.parseColor("#8275DF")
canvas.drawLine(x0, y0, x0 - (x1 - x0), y0 - (y1 - y0), mPaint)
// var angle = mSweepAngle * 1f / mSection
// for (i in 0..mSection - 1) {
// canvas.rotate(angle, mCenterX, mCenterY)
// canvas.drawLine(x0, y0, x1, y1, mPaint)
// }
canvas.rotate(mSweepAngle, mCenterX, mCenterY)
mPaint.color = Color.parseColor("#25C7AD")
canvas.drawLine(x0, y0, x0 - (x1 - x0), y0 - (y1 - y0), mPaint)
canvas.restore()
/**
* 画短刻度
* 同样采用canvas的旋转原理
*/
// canvas.save()
// mPaint.strokeWidth = mStrokeWidth / 2f
// val x2 = (mPadding + mStrokeWidth + mRadius - (mRadius - 2 * mLength1 / 3f) * cos).toFloat()
// val y2 = (mPadding + mStrokeWidth + mRadius - (mRadius - 2 * mLength1 / 3f) * sin).toFloat()
// canvas.drawLine(x0, y0, x2, y2, mPaint)
// angle = mSweepAngle * 1f / (mSection * mPortion)
// for (i in 1..(mSection * mPortion - 1)) {
// canvas.rotate(angle, mCenterX, mCenterY)
// if (i % mPortion == 0) {
// continue
// }
// canvas.drawLine(x0, y0, x2, y2, mPaint)
// }
// canvas.restore()
/**
* 画长刻度读数
*/
mPaint.textSize = sp2px(14).toFloat()
mPaint.style = Paint.Style.FILL
mPaint.color = ContextCompat.getColor(mContext, R.color.gray)
var α: Float
var p: Array<Float>
val angle = mSweepAngle * 1f / mSection
for (i in 0..mSection) {
α = mStartAngle + angle * i
p = getCoordinatePoint(mRadius - mLength2, α)
if (α % 360 > 135 && α % 360 < 225) {
mPaint.textAlign = Paint.Align.LEFT
} else if ((α % 360 >= 0 && α % 360 < 45) || (α % 360 > 315 && α % 360 <= 360)) {
mPaint.textAlign = Paint.Align.RIGHT
} else {
mPaint.textAlign = Paint.Align.CENTER
}
if (!TextUtils.isEmpty(mHeaderText)) {
mPaint.getTextBounds(mHeaderText, 0, mTexts[i].length, mRectText)
}
val txtH = mRectText.height()
if (i <= 1 || i >= mSection - 1) {
canvas.drawText(mTexts[i], p[0], (p[1] + txtH / 2), mPaint)
} else if (i == 3) {
// LogUtil.d(" α -> " + α)
// LogUtil.d("AAA ->" + mTexts[i] + "x -> " + p[0] + "y -> " + p[1])
canvas.drawText(
mTexts[i], p[0] + txtH / 2,
p[1] + txtH, mPaint
)
} else if (i == mSection - 3) {
// LogUtil.d(" α -> " + α)
// LogUtil.d("BBB ->" + mTexts[i] + "x -> " + p[0] + "y -> " + p[1])
canvas.drawText(
mTexts[i],
p[0] - txtH / 2, p[1] + txtH, mPaint
)
} else {
canvas.drawText(mTexts[i], p[0], (p[1] + txtH), mPaint)
}
}
mPaint.color = ContextCompat.getColor(mContext, R.color.gray)
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.style = Paint.Style.FILL
mPaint.shader = null
/** 画表头 没有表头就不画 */
if (!TextUtils.isEmpty(mHeaderText)) {
mPaint.textSize = sp2px(14).toFloat()
mPaint.textAlign = Paint.Align.CENTER
mPaint.getTextBounds(mHeaderText, 0, mHeaderText.length, mRectText)
canvas.drawText(mHeaderText, mCenterX, mCenterY - mRectText.height() * 3, mPaint)
}
// mCenterY += 20
val a = mStartAngle + mSweepAngle * (mVelocity - mMin) / (mMax - mMin) // 指针与水平线夹角
val d = dp2px(10) // 指针由两个等腰三角形构成,d为共底边长的一半
mPath.reset()
val p1 = getCoordinatePoint(mPLRadius, a)
mPath.moveTo(p1[0], p1[1])
val p2 = getCoordinatePoint(d, a - 90)
mPath.lineTo(p2[0], p2[1])
val p3 = getCoordinatePoint(d, a + 90)
mPath.lineTo(p3[0], p3[1])
mPath.close()
mGradient = LinearGradient(
p1[0],
p1[1],
p3[0],
p3[1],
Color.parseColor("#FE9D75"),
Color.TRANSPARENT,
Shader.TileMode.MIRROR
)
mPaint.shader = mGradient
canvas.drawPath(mPath, mPaint)
mPaint.shader = null
/** 画指针 */
mPaint.color = Color.WHITE
mPaint.setShadowLayer(10f, 0f, 0f, Color.parseColor("#E2E2E2"));
canvas.drawCircle(mCenterX, mCenterY, dp2px(60).toFloat(), mPaint)//中心圆
// 去除阴影
mPaint.setShadowLayer(0f, 0f, 0f, Color.GRAY);
//
// mGradient = SweepGradient(
// 0f,
// 0f,
// Color.parseColor("#BEC2CC"),
// Color.TRANSPARENT,
// )
// mPaint.shader = mGradient
// mPaint.style = Paint.Style.STROKE
// mPaint.strokeWidth = dp2px(2).toFloat()
// canvas.drawCircle(mCenterX, mCenterY, dp2px(60).toFloat(), mPaint)//空心圆
// mPaint.shader = null
mPaint.style = Paint.Style.FILL
mPaint.textSize = sp2px(22).toFloat()
mPaint.textAlign = Paint.Align.CENTER
mPaint.typeface = Typeface.DEFAULT_BOLD
mPaint.color = ContextCompat.getColor(mContext, R.color.black)
canvas.drawText("$mReadSpeed", mCenterX, mCenterY - mRectText.height() / 2, mPaint)
mPaint.textSize = sp2px(14).toFloat()
mPaint.typeface = Typeface.DEFAULT
canvas.drawText(
mReadSpeedStr,
mCenterX,
mCenterY + mRectText.height() * 2,
mPaint
)
// mCenterY -= 20
// canvas.drawLine(p1[0], p1[1], mCenterX, mCenterY, mPaint)
// val p2 = getCoordinatePoint(mPSRadius, θ + 180)
// canvas.drawLine(mCenterX, mCenterY, p2[0], p2[1], mPaint)
}
/** 获取坐标 */
fun getCoordinatePoint(radius: Int, angle: Float): Array<Float> {
val point = arrayOf(0f, 0f)
var arcAngle = Math.toRadians(angle.toDouble())
if (angle < 90f) {
point[0] = (mCenterX + cos(arcAngle) * radius).toFloat()
point[1] = (mCenterY + sin(arcAngle) * radius).toFloat()
} else if (angle == 90f) {
point[0] = mCenterX
point[1] = mCenterY + radius
} else if (angle > 90f && angle < 180f) {
arcAngle = Math.PI * (180f - angle) / 180
point[0] = (mCenterX - cos(arcAngle) * radius).toFloat()
point[1] = (mCenterY + sin(arcAngle) * radius).toFloat()
} else if (angle == 180f) {
point[0] = mCenterX - radius
point[1] = mCenterY
} else if (angle > 180f && angle < 270f) {
arcAngle = Math.PI * (angle - 180f) / 180
point[0] = (mCenterX - cos(arcAngle) * radius).toFloat()
point[1] = (mCenterY - sin(arcAngle) * radius).toFloat()
} else if (angle == 270f) {
point[0] = mCenterX
point[1] = mCenterY - radius
} else {
arcAngle = Math.PI * (360 - angle) / 180.0
point[0] = (mCenterX + cos(arcAngle) * radius).toFloat()
point[1] = (mCenterY - sin(arcAngle) * radius).toFloat()
}
return point
}
fun getVelocity(): Int = mVelocity
fun setVelocity(velocity: Int, speed: Float) {
mSpeed = String.format("%.2f", speed / 1024).toFloat()
if (speed > 1024) {
mReadSpeed = mSpeed
mReadSpeedStr = "Mb/s"
} else {
mReadSpeed = speed
mReadSpeedStr = "Kb/s"
}
if (mVelocity == velocity || velocity < mMin || velocity > mMax) {
return
}
mVelocity = velocity
postInvalidate()
}
fun getmMax(): Int {
return mMax
}
fun getmSection(): Int {
return mSection
}
fun getmPortion(): Int {
return mPortion
}
private fun dp2px(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
private fun sp2px(sp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, sp.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import com.mints.wisdomclean.R;
import com.mints.wisdomclean.utils.BubbleUtils;
/**
* DrawHook
* Created by Zane on 2015/3/4.
*/
public class DrawHookView extends View {
//绘制圆弧的进度值
private int progress = 0;
//线1的x轴
private int line1_x = 0;
//线1的y轴
private int line1_y = 0;
//线2的x轴
private int line2_x = 0;
//线2的y轴
private int line2_y = 0;
private final Paint paint;
public DrawHookView(Context context) {
this(context, null);
}
public DrawHookView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawHookView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DrawHookView, defStyle, 0);
int color = a.getColor(R.styleable.DrawHookView_dhv_color, Color.WHITE);
a.recycle();
paint = new Paint();
//设置画笔颜色
paint.setColor(getResources().getColor(R.color.white));
//设置圆弧的宽度
paint.setStrokeWidth(BubbleUtils.dp2px(3));
//设置圆弧为空心
paint.setStyle(Paint.Style.STROKE);
//消除锯齿
paint.setAntiAlias(true);
//圆润边
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setColor(color);
}
//绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
progress += 2;
//获取圆心的x坐标
int center = getWidth() / 2;
int center1 = center - getWidth() / 4;
//圆弧半径
int radius = getWidth() / 2 - 5;
//定义的圆弧的形状和大小的界限
RectF rectF = new RectF(center - radius - 1, center - radius - 1, center + radius + 1, center + radius + 1);
//根据进度画圆弧
canvas.drawArc(rectF, 180, 360 * progress / 100, false, paint);
/*
* 绘制对勾
*/
//先等圆弧画完,才话对勾
if (progress >= 100) {
if (line1_x < radius / 3) {
line1_x++;
line1_y++;
}
//画第一根线
canvas.drawLine(center1, center, center1 + line1_x, center + line1_y, paint);
if (line1_x == radius / 3) {
line2_x = line1_x;
line2_y = line1_y;
line1_x++;
line1_y++;
}
if (line1_x >= radius / 3 && line2_x <= radius) {
line2_x++;
line2_y--;
}
//画第二根线
canvas.drawLine(center1 + line1_x, center + line1_y, center1 + line2_x, center + line2_y, paint);
}
//每隔10毫秒界面刷新
postInvalidateDelayed(6);
}
}
package com.mints.wisdomclean.ui.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.GridView;
public class ExpandableGridView extends GridView {
boolean expanded = true;
public boolean isExpanded() {
return expanded;
}
public ExpandableGridView(Context context) {
super(context);
}
public ExpandableGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandableGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isExpanded()) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
}
package com.mints.wisdomclean.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Movie
import android.os.Build
import android.util.AttributeSet
import android.view.View
import com.mints.wisdomclean.R
/**
* Created by Cuneyt on 4.10.2015.
* Updated by Cuneyt on 02.04.2019.
* Gifview
*/
class GifView @JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = R.styleable.CustomTheme_gifViewStyle
) : View(context, attrs, defStyle) {
private var movieMovieResourceId: Int = 0
private var movie: Movie? = null
private var movieStart: Long = 0
private var currentAnimationTime: Int = 0
private var movieLeft: Float = 0F
private var movieTop: Float = 0F
private var movieScale: Float = 0F
private var movieMeasuredMovieWidth: Int = 0
private var movieMeasuredMovieHeight: Int = 0
@Volatile
var isPaused: Boolean = false
private var isVisible = true
var gifResource: Int
get() = this.movieMovieResourceId
set(movieResourceId) {
this.movieMovieResourceId = movieResourceId
movie = Movie.decodeStream(resources.openRawResource(movieMovieResourceId))
requestLayout()
}
val isPlaying: Boolean
get() = !this.isPaused
init {
setViewAttributes(context, attrs, defStyle)
}
@SuppressLint("NewApi")
private fun setViewAttributes(context: Context, attrs: AttributeSet?, defStyle: Int) {
setLayerType(LAYER_TYPE_SOFTWARE, null)
val array = context.obtainStyledAttributes(
attrs,
R.styleable.GifView, defStyle, R.style.Widget_GifView
)
//-1 is default value
movieMovieResourceId = array.getResourceId(R.styleable.GifView_src, -1)
isPaused = array.getBoolean(R.styleable.GifView_paused, false)
array.recycle()
if (movieMovieResourceId != -1) {
movie = Movie.decodeStream(resources.openRawResource(movieMovieResourceId))
}
}
fun play() {
if (this.isPaused) {
this.isPaused = false
/**
* Calculate new movie start time, so that it resumes from the same
* frame.
*/
movieStart = android.os.SystemClock.uptimeMillis() - currentAnimationTime
invalidate()
}
}
fun pause() {
if (!this.isPaused) {
this.isPaused = true
invalidate()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (movie != null) {
val movieWidth = movie!!.width()
val movieHeight = movie!!.height()
/*
* Calculate horizontal scaling
*/
var scaleH = 1f
val measureModeWidth = MeasureSpec.getMode(widthMeasureSpec)
if (measureModeWidth != MeasureSpec.UNSPECIFIED) {
val maximumWidth = MeasureSpec.getSize(widthMeasureSpec)
if (movieWidth > maximumWidth * 2) {
scaleH = movieWidth.toFloat() / maximumWidth.toFloat()
} else {
scaleH = movieWidth.toFloat() / maximumWidth.toFloat()
}
// if (movieWidth > maximumWidth) {
// scaleH = movieWidth.toFloat() / maximumWidth.toFloat()
// }
}
/*
* calculate vertical scaling
*/
var scaleW = 1f
val measureModeHeight = MeasureSpec.getMode(heightMeasureSpec)
if (measureModeHeight != MeasureSpec.UNSPECIFIED) {
val maximumHeight = MeasureSpec.getSize(heightMeasureSpec)
if (movieHeight > maximumHeight * 2) {
scaleW = movieHeight.toFloat() / maximumHeight.toFloat()
} else {
scaleW = movieHeight.toFloat() / maximumHeight.toFloat()
}
// if (movieHeight > maximumHeight) {
// scaleW = movieHeight.toFloat() / maximumHeight.toFloat()
// }
}
// LogUtil.d("AAAAAAA" + scaleH)
// LogUtil.d("BBBBBB" + scaleW)
/*
* calculate overall scale
*/
if (scaleH > 1f || scaleW > 1f || scaleW < 0.5f || scaleH < 0.5f) {
movieScale = 1f / scaleH.coerceAtLeast(scaleW)
} else {
movieScale = 1f / scaleH.coerceAtMost(scaleW)
}
movieMeasuredMovieWidth = (movieWidth * movieScale).toInt()
movieMeasuredMovieHeight = (movieHeight * movieScale).toInt()
setMeasuredDimension(movieMeasuredMovieWidth, movieMeasuredMovieHeight)
} else {
/*
* No movie set, just set minimum available size.
*/
setMeasuredDimension(suggestedMinimumWidth, suggestedMinimumHeight)
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
/*
* Calculate movieLeft / movieTop for drawing in center
*/
movieLeft = (width - movieMeasuredMovieWidth) / 2f
movieTop = (height - movieMeasuredMovieHeight) / 2f
isVisible = visibility == VISIBLE
}
override fun onDraw(canvas: Canvas) {
if (movie != null) {
if (!isPaused) {
updateAnimationTime()
drawMovieFrame(canvas)
invalidateView()
} else {
drawMovieFrame(canvas)
}
}
}
/**
* Invalidates view only if it is isVisible.
* <br></br>
* [.postInvalidateOnAnimation] is used for Jelly Bean and higher.
*/
@SuppressLint("NewApi")
private fun invalidateView() {
if (isVisible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postInvalidateOnAnimation()
} else {
invalidate()
}
}
}
/**
* Calculate current animation time
*/
private fun updateAnimationTime() {
val now = android.os.SystemClock.uptimeMillis()
if (movieStart == 0L) {
movieStart = now
}
var duration = movie!!.duration()
if (duration == 0) {
duration = DEFAULT_MOVIE_VIEW_DURATION
}
currentAnimationTime = ((now - movieStart) % duration).toInt()
}
/**
* Draw current GIF frame
*/
private fun drawMovieFrame(canvas: Canvas) {
movie!!.setTime(currentAnimationTime)
canvas.save()
canvas.scale(movieScale, movieScale)
movie!!.draw(canvas, movieLeft / movieScale, movieTop / movieScale)
canvas.restore()
}
@SuppressLint("NewApi")
override fun onScreenStateChanged(screenState: Int) {
super.onScreenStateChanged(screenState)
isVisible = screenState == SCREEN_STATE_ON
invalidateView()
}
@SuppressLint("NewApi")
override fun onVisibilityChanged(changedView: View, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
isVisible = visibility == VISIBLE
invalidateView()
}
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(visibility)
isVisible = visibility == VISIBLE
invalidateView()
}
companion object {
private const val DEFAULT_MOVIE_VIEW_DURATION = 1000
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
public class InterceptFrameLayout extends FrameLayout {
boolean isIntercept = true;
public boolean isInterceptTouch() {
return isIntercept;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return isIntercept;
}
public InterceptFrameLayout(Context context) {
super(context);
}
public InterceptFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public InterceptFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void isIntercept(boolean isIntercept) {
this.isIntercept = isIntercept;
}
}
package com.mints.wisdomclean.ui.widgets;
import android.os.CountDownTimer;
import android.widget.TextView;
/**
* 简单 时,分,秒,毫秒倒计时
* <p>
* 精确到毫秒
*/
public class MilliCountDownTimer extends CountDownTimer {
// 默认倒计时间隔
private static final long DEFAULT_INTERVAL = 100L;
/**
* 秒,分,时对应的毫秒数
*/
private int sec = 1000, min = sec * 60, hr = min * 60;
/**
* 显示时间的视图
*/
private TextView tvDisplay;
/**
* 结束监听
*/
private static OnFinishListener onFinishListener;
/**
* @param millisInFuture 倒计时总时间 单位:毫秒
* The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval 倒计时间隔 单位:毫秒
* The interval along the way to receive
* {@link #onTick(long)} callbacks.
* @param tvDisplay 显示时间的视图
*/
public MilliCountDownTimer(long millisInFuture, long countDownInterval, TextView tvDisplay) {
super(millisInFuture, countDownInterval);
this.tvDisplay = tvDisplay;
}
/**
* @param millisInFuture 倒计时总时间 单位:毫秒
* The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param tvDisplay 显示时间的视图
*/
public MilliCountDownTimer(long millisInFuture, TextView tvDisplay) {
super(millisInFuture, DEFAULT_INTERVAL);
this.tvDisplay = tvDisplay;
}
/**
* 每一次间隔时间到后调用
*
* @param millisUntilFinished 剩余总时间 单位:毫秒
*/
@Override
public void onTick(long millisUntilFinished) {
// 剩余的小时,分钟,秒,毫秒
long lHr = millisUntilFinished / hr;
long lMin = (millisUntilFinished - lHr * hr) / min;
long lSec = (millisUntilFinished - lHr * hr - lMin * min) / sec;
long lMs = millisUntilFinished - lHr * hr - lMin * min - lSec * sec;
String strLHr = getTime(lHr);
String strLMin = getTime(lMin);
String strLSec = getTime(lSec);
String strLMs = getMs(lMs);
// 依次拼接时间 时:分:秒:毫秒
tvDisplay.setText(strLHr + "时" + strLMin + "分" + strLSec + "秒" + strLMs);
}
/**
* 根据毫秒换算成相应单位后是否大于10来返回相应时间
*/
private String getTime(long time) {
return time > 10 ? String.valueOf(time) : "0" + time;
}
/**
* 获取毫秒
*/
private String getMs(long time) {
String strMs = String.valueOf(time);
return time > 100 ? strMs.substring(0, strMs.length() - 1) : "00";
}
/**
* 结束监听,可以在倒计时结束时做一些事
*/
public interface OnFinishListener {
void onFinish();
}
/**
* 设置结束监听
*
* @param onFinishListener 结束监听对象
*/
public MilliCountDownTimer setOnFinishListener(OnFinishListener onFinishListener) {
MilliCountDownTimer.onFinishListener = onFinishListener;
return this;
}
/**
* 倒计时结束时调用
*/
@Override
public void onFinish() {
tvDisplay.setText("00:00:00");
if (onFinishListener != null)
onFinishListener.onFinish();
}
}
package com.mints.wisdomclean.ui.widgets
import android.app.Dialog
import android.content.Context
import android.view.*
import android.widget.LinearLayout
import android.widget.TextView
import com.mints.wisdomclean.R
class PhotoDialog(context: Context) : Dialog(context, R.style.dialog) {
private val llDialogCamera: LinearLayout
private val llDialogAlbum: LinearLayout
private val tvDialogCancel: TextView
private var mOnChangePhotoListener: OnChangePhotoListener? = null
init {
setContentView(R.layout.dialog_photo)
// 设置window属性
window?.let {
val lp = it.attributes
lp.gravity = Gravity.BOTTOM
lp.width = WindowManager.LayoutParams.MATCH_PARENT
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
lp.windowAnimations = R.style.DialogAnimBottom
it.attributes = lp
}
// 设置外部不可关闭
// setCancelable(false)
// setCanceledOnTouchOutside(false)
setOnKeyListener { _, i, _ ->
i == KeyEvent.KEYCODE_BACK
}
llDialogCamera = findViewById<View>(R.id.llDialogCamera) as LinearLayout
llDialogAlbum = findViewById<View>(R.id.llDialogAlbum) as LinearLayout
tvDialogCancel = findViewById<View>(R.id.tvDialogCancel) as TextView
llDialogCamera.setOnClickListener {
mOnChangePhotoListener?.onChangePhoto(0)
}
llDialogAlbum.setOnClickListener {
mOnChangePhotoListener?.onChangePhoto(1)
}
tvDialogCancel.setOnClickListener {
dismiss()
}
}
fun setOnChangePhotoListener(onChangePhotoListener: OnChangePhotoListener): PhotoDialog {
this.mOnChangePhotoListener = onChangePhotoListener
return this
}
interface OnChangePhotoListener {
fun onChangePhoto(status: Int)
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.text.TextPaint
import android.util.AttributeSet
import android.view.View
class PhotoWrpView : View {
constructor(context: Context) : this(context, null)
constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
constructor(context: Context, attributeSet: AttributeSet?, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mTextPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
private var mWaterMarkPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
private var mWaterMarkOnOff = false
private var mBitmap: Bitmap? = null
private var mWidth = 0f
private var mHeight = 0f
private val mPaintWidth = 2f
private var mCurrentPhotoInch = PHOTO_INCH_ONE
private var mTopStr: String? = null
private var mRightStr: String? = null
private var mBottomStr: String? = null
private var mLeftStr: String? = null
private var mArr: IntArray? = null
companion object {
/**
* 一寸:25mm*35mm 295*413px
* 二寸:35mm*49mm 413*579px
* 小二寸:35mm*45mm 413*531px
* 大一寸:33mm*48mm 390*567px
*/
const val PHOTO_INCH_ONE = 1 // 一寸
const val PHOTO_INCH_TWO = 2 // 二寸
const val PHOTO_INCH_BIG_ONE = 3 // 大一寸
const val PHOTO_INCH_SMALL_TWO = 5 // 小二寸
const val WATER_MARK_STR = "保存后不显示此文字"
}
init {
mPaint.strokeWidth = mPaintWidth
mPaint.color = Color.GRAY
mPaint.style = Paint.Style.STROKE
mTextPaint.strokeWidth = 1f
mTextPaint.textSize = dp2px(context, 12f)
mTextPaint.textAlign = Paint.Align.CENTER
mTextPaint.color = Color.GRAY
mTextPaint.style = Paint.Style.FILL
mWaterMarkPaint.strokeWidth = 10f
mWaterMarkPaint.textSize = dp2px(context, 18f)
mWaterMarkPaint.color = Color.GRAY
mWaterMarkPaint.style = Paint.Style.FILL
}
private fun dp2px(context: Context, pxVal: Float): Float {
return pxVal * context.resources.displayMetrics.scaledDensity + 0.5f
}
/**
*
* @param bitmap
* @param photoInch
*/
fun setBitmap(bitmap: Bitmap?, photoInch: Int) {
if (bitmap == null) {
return
}
mCurrentPhotoInch = photoInch
mArr = calculatePosition()
//获取图片的宽高
mBitmap = getNewBitmap(bitmap, mArr!![0], mArr!![1])
invalidate()
}
private fun getNewBitmap(bitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap? {
// 获得图片的宽高.
val width = bitmap.width
val height = bitmap.height
// 计算缩放比例.
val scaleWidth = newWidth.toFloat() / width
val scaleHeight = newHeight.toFloat() / height
// 取得想要缩放的matrix参数.
val matrix = Matrix()
matrix.postScale(scaleWidth, scaleHeight)
// 得到新的图片.
return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
}
fun turnOnWaterMark(onOff: Boolean) {
mWaterMarkOnOff = onOff
invalidate()
}
private fun calculatePosition(): IntArray {
var scaleWidth = 0
var scaleHeight = 0
when (mCurrentPhotoInch) {
PHOTO_INCH_ONE -> {
mTopStr = "295px"
mRightStr = "413px"
mBottomStr = "25mm"
mLeftStr = "35mm"
scaleWidth = (mWidth * 0.6).toInt()
scaleHeight = (scaleWidth * 1.4).toInt()
}
PHOTO_INCH_TWO -> {
mTopStr = "413px"
mRightStr = "579px"
mBottomStr = "35mm"
mLeftStr = "49mm"
scaleWidth = (mWidth * 0.6).toInt()
scaleHeight = (scaleWidth * 1.45).toInt()
}
PHOTO_INCH_BIG_ONE -> {
mTopStr = "390px"
mRightStr = "567px"
mBottomStr = "33mm"
mLeftStr = "48mm"
scaleWidth = (mWidth * 0.6).toInt()
scaleHeight = (scaleWidth * 1.45).toInt()
}
PHOTO_INCH_SMALL_TWO -> {
mTopStr = "413px"
mRightStr = "531px"
mBottomStr = "35mm"
mLeftStr = "45mm"
scaleWidth = (mWidth * 0.6).toInt()
scaleHeight = (scaleWidth * 1.4).toInt()
}
}
return intArrayOf(scaleWidth, scaleHeight)
}
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (mBitmap == null) return
val arr = mArr!!
// 计算左边位置
val left = (mWidth - arr[0]) / 2
// 计算上边位置
val top = (mHeight - arr[1]) / 2
val destRect = Rect(left.toInt(), top.toInt(), (left + arr[0]).toInt(), (top + arr[1]).toInt())
val srcRect = Rect(0, 0, arr[0], arr[1])
canvas.drawBitmap(mBitmap!!, srcRect, destRect, mPaint)
// 上方
canvas.drawLine(left, (top - 50), left, top, mPaint)
canvas.drawLine(left, (top - 40), (left + arr[0] / 3), (top - 40), mPaint)
canvas.drawLine((left + arr[0] / 3 * 2), (top - 40), (left + arr[0]), (top - 40), mPaint)
canvas.drawLine((left + arr[0]), (top - 50), (left + arr[0]), top, mPaint)
canvas.drawText(mTopStr!!, mWidth / 2, (top - 30), mTextPaint)
// 右方
canvas.drawLine((left + arr[0]), top, (left + arr[0] + 50), top, mPaint)
canvas.drawLine((left + arr[0] + 40), top, (left + arr[0] + 40), (top + arr[1] / 3), mPaint)
canvas.drawLine((left + arr[0]), (top + arr[1]), (left + arr[0] + 50), (top + arr[1]), mPaint)
canvas.drawLine((left + arr[0] + 40), (top + arr[1] / 3 * 2), (left + arr[0] + 40), (top + arr[1]), mPaint)
drawAngleText(canvas, mRightStr!!, (left + arr[0] + 50), mHeight / 2, mTextPaint, -90f)
// 下方
canvas.drawLine(left, (top + arr[1]), left, (top + arr[1] + 50), mPaint)
canvas.drawLine(left, (top + arr[1] + 40), (left + arr[0] / 3), (top + arr[1] + 40), mPaint)
canvas.drawLine((left + arr[0] / 3 * 2), (top + arr[1] + 40), (left + arr[0]), (top + arr[1] + 40), mPaint)
canvas.drawLine((left + arr[0]), (top + arr[1]), (left + arr[0]), (top + arr[1] + 50), mPaint)
canvas.drawText(mBottomStr!!, mWidth / 2, (top + arr[1] + 50), mTextPaint)
// 左方
canvas.drawLine((left - 50), top, left, top, mPaint)
canvas.drawLine((left - 40), top, (left - 40), (top + arr[1] / 3), mPaint)
canvas.drawLine((left - 40), (top + arr[1] / 3 * 2), (left - 40), (top + arr[1]), mPaint)
canvas.drawLine((left - 50), (top + arr[1]), left, (top + arr[1]), mPaint)
drawAngleText(canvas, mLeftStr!!, (left - 30), mHeight / 2, mTextPaint, -90f)
// if (mWaterMarkOnOff) {
drawAngleText(canvas, WATER_MARK_STR, left + arr[0] / 6, top + arr[1] / 6 * 3, mWaterMarkPaint, -30f)
drawAngleText(canvas, WATER_MARK_STR, left + arr[0] / 6, top + arr[1] / 6 * 4, mWaterMarkPaint, -30f)
drawAngleText(canvas, WATER_MARK_STR, left + arr[0] / 6, top + arr[1] / 6 * 5, mWaterMarkPaint, -30f)
// }
}
private fun drawAngleText(canvas: Canvas, text: String, x: Float, y: Float, paint: Paint, angle: Float) {
if (angle != 0f) {
canvas.rotate(angle, x, y)
}
canvas.drawText(text, x, y, paint)
if (angle != 0f) {
canvas.rotate(-angle, x, y)
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val minimumWidth = suggestedMinimumWidth
val minimumHeight = suggestedMinimumHeight
val width = measureWidth(minimumWidth, widthMeasureSpec)
val height = measureHeight(minimumHeight, heightMeasureSpec)
mWidth = width.toFloat()
mHeight = height.toFloat()
setMeasuredDimension(width, height)
}
private fun measureWidth(defaultWidth: Int, measureSpec: Int): Int {
var measureWidth = defaultWidth
val specMode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)
when (specMode) {
MeasureSpec.AT_MOST -> {
measureWidth = specSize + paddingLeft + paddingRight
}
MeasureSpec.EXACTLY -> {
measureWidth = specSize
}
MeasureSpec.UNSPECIFIED -> {
measureWidth = defaultWidth.coerceAtLeast(specSize)
}
}
return measureWidth
}
private fun measureHeight(defaultHeight: Int, measureSpec: Int): Int {
var measureHeight = defaultHeight
val specMode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)
when (specMode) {
MeasureSpec.AT_MOST -> {
measureHeight = specSize + paddingTop + paddingBottom
}
MeasureSpec.EXACTLY -> {
measureHeight = specSize
}
MeasureSpec.UNSPECIFIED -> {
measureHeight = defaultHeight.coerceAtLeast(specSize)
}
}
return measureHeight
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatButton;
import androidx.core.content.ContextCompat;
import com.mints.wisdomclean.R;
public class ProgressButton extends AppCompatButton {
private float mCornerRadius = 15;
private float mProgressMargin = 0;
private boolean mFinish;
private int mProgress;
private int mMaxProgress = 100;
private int mMinProgress = 0;
private boolean isCycle = false;
private GradientDrawable mDrawableButton;
private GradientDrawable mDrawableProgressBackground;
private GradientDrawable mDrawableProgress;
public ProgressButton(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public ProgressButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
mDrawableProgressBackground = new GradientDrawable();
mDrawableProgress = new GradientDrawable();
mDrawableButton = new GradientDrawable();
int defaultButtonColor = ContextCompat.getColor(context, R.color.color_main);
int defaultProgressColor = ContextCompat.getColor(context, R.color.color_10000000);
int defaultBackColor = ContextCompat.getColor(context, R.color.color_main);
TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.ProgressButton);
try {
mProgressMargin = attr.getDimension(R.styleable.ProgressButton_progressMargin, mProgressMargin);
mCornerRadius = attr.getDimension(R.styleable.ProgressButton_cornerRadius, mCornerRadius);
//Get custom normal color
int buttonColor = attr.getColor(R.styleable.ProgressButton_buttonColor, defaultButtonColor);
//Set normal color
mDrawableButton.setColor(buttonColor);
//Get custom progress background color
int progressBackColor = attr.getColor(R.styleable.ProgressButton_progressBackColor, defaultBackColor);
//Set progress background drawable color
mDrawableProgressBackground.setColor(progressBackColor);
//Get custom progress color
int progressColor = attr.getColor(R.styleable.ProgressButton_progressColor, defaultProgressColor);
//Set progress drawable color
mDrawableProgress.setColor(progressColor);
//Get default progress
mProgress = attr.getInteger(R.styleable.ProgressButton_progress, mProgress);
//Get minimum progress
mMinProgress = attr.getInteger(R.styleable.ProgressButton_minProgress, mMinProgress);
//Get maximize progress
mMaxProgress = attr.getInteger(R.styleable.ProgressButton_maxProgress, mMaxProgress);
} finally {
attr.recycle();
}
//Set corner radius
mDrawableButton.setCornerRadius(mCornerRadius);
mDrawableProgressBackground.setCornerRadius(mCornerRadius);
mDrawableProgress.setCornerRadius(mCornerRadius - mProgressMargin);
setBackgroundDrawable(mDrawableButton);
mFinish = false;
}
@Override
protected void onDraw(Canvas canvas) {
if (isCycle) {
if (mProgress >= 100) {
mProgress = 0;
}
mProgress++;
postInvalidateDelayed(10);
}
if (mProgress > mMinProgress && mProgress <= mMaxProgress) {
float progressWidth =
(float) getMeasuredWidth() * ((float) (mProgress - mMinProgress) / mMaxProgress - mMinProgress);
if (progressWidth < mCornerRadius * 2) {
progressWidth = mCornerRadius * 2;
}
mDrawableProgress.setBounds((int) mProgressMargin, (int) mProgressMargin,
(int) (progressWidth - mProgressMargin), getMeasuredHeight() - (int) mProgressMargin);
mDrawableProgress.draw(canvas);
if (mProgress == mMaxProgress) {
setBackgroundDrawable(mDrawableButton);
// mFinish = true;
}
}
super.onDraw(canvas);
}
public void startCycle() {
isCycle = true;
mProgress = 0;
invalidate();
}
public void pauseCycle() {
isCycle = false;
mProgress = 0;
invalidate();
}
/**
* Set current progress
*/
public void setProgress(int progress) {
if (!mFinish) {
mProgress = progress;
setBackgroundDrawable(mDrawableProgressBackground);
invalidate();
}
}
public void setMaxProgress(int maxProgress) {
mMaxProgress = maxProgress;
}
public void setMinProgress(int minProgress) {
mMinProgress = minProgress;
}
public void reset() {
mFinish = false;
mProgress = mMinProgress;
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.mints.wisdomclean.R;
public class RoundAngleImageView extends ImageView {
private Paint paint;
/**
* 个人理解是
*
* 这两个都是画圆的半径
*/
private int roundWidth = 20;
private int roundHeight = 20;
private Paint paint2;
public RoundAngleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public RoundAngleImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundAngleImageView(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundAngleImageView);
roundWidth = a.getDimensionPixelSize(R.styleable.RoundAngleImageView_roundWidth, roundWidth);
roundHeight = a.getDimensionPixelSize(R.styleable.RoundAngleImageView_roundHeight, roundHeight);
} else {
float density = context.getResources().getDisplayMetrics().density;
roundWidth = (int) (roundWidth * density);
roundHeight = (int) (roundHeight * density);
}
paint = new Paint();
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
paint2 = new Paint();
paint2.setXfermode(null);
}
@Override
public void draw(Canvas canvas) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas2 = new Canvas(bitmap);
super.draw(canvas2);
drawLiftUp(canvas2);
drawLiftDown(canvas2);
drawRightUp(canvas2);
drawRightDown(canvas2);
canvas.drawBitmap(bitmap, 0, 0, paint2);
bitmap.recycle();
}
private void drawLiftUp(Canvas canvas) {
Path path = new Path();
path.moveTo(0, roundHeight);
path.lineTo(0, 0);
path.lineTo(roundWidth, 0);
path.arcTo(new RectF(0, 0, roundWidth * 2, roundHeight * 2), -90, -90);
path.close();
canvas.drawPath(path, paint);
}
private void drawLiftDown(Canvas canvas) {
Path path = new Path();
path.moveTo(0, getHeight() - roundHeight);
path.lineTo(0, getHeight());
path.lineTo(roundWidth, getHeight());
path.arcTo(new RectF(0, getHeight() - roundHeight * 2, roundWidth * 2, getHeight()), 90, 90);
path.close();
canvas.drawPath(path, paint);
}
private void drawRightDown(Canvas canvas) {
Path path = new Path();
path.moveTo(getWidth() - roundWidth, getHeight());
path.lineTo(getWidth(), getHeight());
path.lineTo(getWidth(), getHeight() - roundHeight);
path.arcTo(new RectF(getWidth() - roundWidth * 2, getHeight() - roundHeight * 2, getWidth(), getHeight()), -0, 90);
path.close();
canvas.drawPath(path, paint);
}
private void drawRightUp(Canvas canvas) {
Path path = new Path();
path.moveTo(getWidth(), roundHeight);
path.lineTo(getWidth(), 0);
path.lineTo(getWidth() - roundWidth, 0);
path.arcTo(new RectF(getWidth() - roundWidth * 2, 0, getWidth(), 0 + roundHeight * 2), -90, 90);
path.close();
canvas.drawPath(path, paint);
}
}
package com.mints.wisdomclean.ui.widgets
import android.content.Context
import android.util.AttributeSet
class RoundCheckBox : androidx.appcompat.widget.AppCompatCheckBox {
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, androidx.appcompat.R.attr.radioButtonStyle)
constructor(context: Context) : this(context, null)
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatImageView;
import com.mints.wisdomclean.R;
/**
* 多点触控加Matrix类实现图片的旋转、缩放、平移
*
* @attr R.styleable#TransformativeImageView_max_scale
* @attr R.styleable#TransformativeImageView_min_scale
* @attr R.styleable#TransformativeImageView_revert_duration
* @attr R.styleable#TransformativeImageView_revert
* @attr R.styleable#TransformativeImageView_scale_center
*/
public class TransformativeImageView extends AppCompatImageView {
private static final String TAG = TransformativeImageView.class.getSimpleName();
private static final float MAX_SCALE_FACTOR = 2.0f; // 默认最大缩放比例为2
private static final float UNSPECIFIED_SCALE_FACTOR = -1f; // 未指定缩放比例
private static final float MIN_SCALE_FACTOR = 1.0f; // 默认最小缩放比例为0.3
// private static final float INIT_SCALE_FACTOR = 1.2f; // 默认适应控件大小后的初始化缩放比例
private static final float INIT_SCALE_FACTOR = 0.5f; // 默认适应控件大小后的初始化缩放比例
private static final int DEFAULT_REVERT_DURATION = 300;
private int mRevertDuration = DEFAULT_REVERT_DURATION; // 回弹动画时间
private float mMaxScaleFactor = MAX_SCALE_FACTOR; // 最大缩放比例
private float mMinScaleFactor = UNSPECIFIED_SCALE_FACTOR; // 此最小缩放比例优先级高于下面两个
private float mVerticalMinScaleFactor = MIN_SCALE_FACTOR; // 图片最初的最小缩放比例
private float mHorizontalMinScaleFactor = MIN_SCALE_FACTOR; // 图片旋转90(或-90)度后的的最小缩放比例
protected Matrix mMatrix = new Matrix(); // 用于图片旋转、平移、缩放的矩阵
protected RectF mImageRect = new RectF(); // 保存图片所在区域矩形,坐标为相对于本View的坐标
private boolean mOpenScaleRevert = false; // 是否开启缩放回弹
private boolean mOpenRotateRevert = false; // 是否开启旋转回弹
private boolean mOpenTranslateRevert = false; // 是否开启平移回弹
private boolean mOpenAnimator = false; // 是否开启动画
private boolean mOpenRotate = true; // 是否开启旋转
private boolean mOpenScale = true; // 是否开启缩放
private boolean mOpenTranslate = true; // 是否开启平移
private boolean mOpenBorder = false; // 是否显示边框
private Paint paint = new Paint();
private float srokeWidth = 2f;
public TransformativeImageView(Context context) {
this(context, null);
}
public TransformativeImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TransformativeImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
obtainAttrs(attrs);
init();
}
private void obtainAttrs(AttributeSet attrs) {
if (attrs == null) return;
TypedArray typedArray = getContext()
.obtainStyledAttributes(attrs, R.styleable.TransformativeImageView);
mMaxScaleFactor = typedArray.getFloat(
R.styleable.TransformativeImageView_max_scale, MAX_SCALE_FACTOR);
// mMinScaleFactor = typedArray.getFloat(
// R.styleable.TransformativeImageView_min_scale, UNSPECIFIED_SCALE_FACTOR);
mMinScaleFactor = typedArray.getFloat(
R.styleable.TransformativeImageView_min_scale, MIN_SCALE_FACTOR);
mRevertDuration = typedArray.getInteger(
R.styleable.TransformativeImageView_revert_duration, DEFAULT_REVERT_DURATION);
mOpenScaleRevert = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_scale_revert, false);
mOpenRotateRevert = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_rotate_revert, false);
mOpenTranslateRevert = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_translate_revert, false);
mOpenAnimator = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_animator, true);
mScaleBy = typedArray.getInt(
R.styleable.TransformativeImageView_scale_center, SCALE_BY_IMAGE_CENTER);
mOpenRotate = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_rotate, true);
mOpenScale = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_scale, true);
mOpenTranslate = typedArray.getBoolean(
R.styleable.TransformativeImageView_open_translate, true);
typedArray.recycle();
}
private void init() {
// FIXME 修复图片锯齿,关闭硬件加速ANTI_ALIAS_FLAG才能生效
// setLayerType(View.LAYER_TYPE_SOFTWARE, null);
setScaleType(ScaleType.MATRIX);
mRevertAnimator.setDuration(mRevertDuration);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(srokeWidth);
paint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
initImgPositionAndSize();
}
/**
* 初始化图片位置和大小
*/
private void initImgPositionAndSize() {
mMatrix.reset();
// 初始化ImageRect
refreshImageRect();
// 计算缩放比例,使图片适应控件大小
mHorizontalMinScaleFactor = Math.min(getWidth() / mImageRect.width(),
getHeight() / mImageRect.height());
mVerticalMinScaleFactor = Math.min(getHeight() / mImageRect.width(),
getWidth() / mImageRect.height());
float scaleFactor = mHorizontalMinScaleFactor;
// 初始图片缩放比例比最小缩放比例稍大
scaleFactor *= INIT_SCALE_FACTOR;
mScaleFactor = scaleFactor;
mMatrix.postScale(scaleFactor, scaleFactor, mImageRect.centerX(), mImageRect.centerY());
refreshImageRect();
// 移动图片到中心
mMatrix.postTranslate((getRight() - getLeft()) / 2 - mImageRect.centerX(),
(getBottom() - getTop()) / 2 - mImageRect.centerY());
applyMatrix();
// 如果用户有指定最小缩放比例则使用用户指定的
if (mMinScaleFactor != UNSPECIFIED_SCALE_FACTOR) {
mHorizontalMinScaleFactor = mMinScaleFactor;
mVerticalMinScaleFactor = mMinScaleFactor;
}
}
private PaintFlagsDrawFilter mDrawFilter =
new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@Override
protected void onDraw(Canvas canvas) {
canvas.setDrawFilter(mDrawFilter);
if (mOpenBorder) {
canvas.drawRect(mImageRect, paint);
}
super.onDraw(canvas);
}
private PointF mLastPoint1 = new PointF(); // 上次事件的第一个触点
private PointF mLastPoint2 = new PointF(); // 上次事件的第二个触点
private PointF mCurrentPoint1 = new PointF(); // 本次事件的第一个触点
private PointF mCurrentPoint2 = new PointF(); // 本次事件的第二个触点
private float mScaleFactor = 1.0f; // 当前的缩放倍数
private boolean mCanScale = false; // 是否可以缩放
protected PointF mLastMidPoint = new PointF(); // 图片平移时记录上一次ACTION_MOVE的点
private PointF mCurrentMidPoint = new PointF(); // 当前各触点的中点
protected boolean mCanDrag = false; // 是否可以平移
private PointF mLastVector = new PointF(); // 记录上一次触摸事件两指所表示的向量
private PointF mCurrentVector = new PointF(); // 记录当前触摸事件两指所表示的向量
private boolean mCanRotate = false; // 判断是否可以旋转
private MatrixRevertAnimator mRevertAnimator = new MatrixRevertAnimator(); // 回弹动画
private float[] mFromMatrixValue = new float[9]; // 动画初始时矩阵值
private float[] mToMatrixValue = new float[9]; // 动画终结时矩阵值
protected boolean isTransforming = false; // 图片是否正在变化
@Override
public boolean onTouchEvent(MotionEvent event) {
PointF midPoint = getMidPointOfFinger(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
// 每次触摸事件开始都初始化mLastMidPonit
mLastMidPoint.set(midPoint);
isTransforming = false;
mRevertAnimator.cancel();
// 新手指落下则需要重新判断是否可以对图片进行变换
mCanRotate = false;
mCanScale = false;
mCanDrag = false;
if (event.getPointerCount() == 2) {
// 旋转、平移、缩放分别使用三个判断变量,避免后期某个操作执行条件改变
mCanScale = true;
mLastPoint1.set(event.getX(0), event.getY(0));
mLastPoint2.set(event.getX(1), event.getY(1));
mCanRotate = true;
mLastVector.set(event.getX(1) - event.getX(0),
event.getY(1) - event.getY(0));
} else if (event.getPointerCount() == 1) {
mCanDrag = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (mCanDrag && mOpenTranslate) translate(midPoint);
if (mCanScale && mOpenScale) scale(event);
if (mCanRotate && mOpenRotate) rotate(event);
// 判断图片是否发生了变换
if (!getImageMatrix().equals(mMatrix)) isTransforming = true;
if (mCanDrag || mCanScale || mCanRotate) applyMatrix();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 检测是否需要回弹
if (mOpenRotateRevert || mOpenScaleRevert || mOpenTranslateRevert) {
mMatrix.getValues(mFromMatrixValue);/*设置矩阵动画初始值*/
/* 旋转和缩放都会影响矩阵,进而影响后续需要使用到ImageRect的地方,
* 所以检测顺序不能改变
*/
if (mOpenRotateRevert) checkRotation();
if (mOpenScaleRevert) checkScale();
if (mOpenTranslateRevert) checkBorder();
mMatrix.getValues(mToMatrixValue);/*设置矩阵动画结束值*/
if (mOpenAnimator) {
// 启动回弹动画
mRevertAnimator.setMatrixValue(mFromMatrixValue, mToMatrixValue);
mRevertAnimator.cancel();
mRevertAnimator.start();
} else {
applyMatrix();
}
}
case MotionEvent.ACTION_POINTER_UP:
mCanScale = false;
mCanDrag = false;
mCanRotate = false;
break;
}
super.onTouchEvent(event);
return true;
}
private void rotate(MotionEvent event) {
// 计算当前两指触点所表示的向量
mCurrentVector.set(event.getX(1) - event.getX(0),
event.getY(1) - event.getY(0));
// 获取旋转角度
float degree = getRotateDegree(mLastVector, mCurrentVector);
mMatrix.postRotate(degree, mImageRect.centerX(), mImageRect.centerY());
mLastVector.set(mCurrentVector);
}
/**
* 使用Math#atan2(double y, double x)方法求上次触摸事件两指所示向量与x轴的夹角,
* 再求出本次触摸事件两指所示向量与x轴夹角,最后求出两角之差即为图片需要转过的角度
*
* @param lastVector 上次触摸事件两指间连线所表示的向量
* @param currentVector 本次触摸事件两指间连线所表示的向量
* @return 两向量夹角,单位“度”,顺时针旋转时为正数,逆时针旋转时返回负数
*/
private float getRotateDegree(PointF lastVector, PointF currentVector) {
//上次触摸事件向量与x轴夹角
double lastRad = Math.atan2(lastVector.y, lastVector.x);
//当前触摸事件向量与x轴夹角
double currentRad = Math.atan2(currentVector.y, currentVector.x);
// 两向量与x轴夹角之差即为需要旋转的角度
double rad = currentRad - lastRad;
//“弧度”转“度”
return (float) Math.toDegrees(rad);
}
protected void translate(PointF midPoint) {
float dx = midPoint.x - mLastMidPoint.x;
float dy = midPoint.y - mLastMidPoint.y;
mMatrix.postTranslate(dx, dy);
mLastMidPoint.set(midPoint);
}
/**
* 计算所有触点的中点
*
* @param event 当前触摸事件
* @return 本次触摸事件所有触点的中点
*/
private PointF getMidPointOfFinger(MotionEvent event) {
// 初始化mCurrentMidPoint
mCurrentMidPoint.set(0f, 0f);
int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
mCurrentMidPoint.x += event.getX(i);
mCurrentMidPoint.y += event.getY(i);
}
mCurrentMidPoint.x /= pointerCount;
mCurrentMidPoint.y /= pointerCount;
return mCurrentMidPoint;
}
private static final int SCALE_BY_IMAGE_CENTER = 0; // 以图片中心为缩放中心
private static final int SCALE_BY_FINGER_MID_POINT = 1; // 以所有手指的中点为缩放中心
private int mScaleBy = SCALE_BY_FINGER_MID_POINT;
private PointF scaleCenter = new PointF();
/**
* 获取图片的缩放中心,该属性可在外部设置,或通过xml文件设置
* 默认中心点为图片中心
*
* @return 图片的缩放中心点
*/
private PointF getScaleCenter() {
// 使用全局变量避免频繁创建变量
switch (mScaleBy) {
case SCALE_BY_IMAGE_CENTER:
scaleCenter.set(mImageRect.centerX(), mImageRect.centerY());
break;
case SCALE_BY_FINGER_MID_POINT:
scaleCenter.set(mLastMidPoint.x, mLastMidPoint.y);
break;
}
return scaleCenter;
}
private void scale(MotionEvent event) {
PointF scaleCenter = getScaleCenter();
// 初始化当前两指触点
mCurrentPoint1.set(event.getX(0), event.getY(0));
mCurrentPoint2.set(event.getX(1), event.getY(1));
// 计算缩放比例
float scaleFactor = distance(mCurrentPoint1, mCurrentPoint2)
/ distance(mLastPoint1, mLastPoint2);
// 更新当前图片的缩放比例
mScaleFactor *= scaleFactor;
mMatrix.postScale(scaleFactor, scaleFactor,
scaleCenter.x, scaleCenter.y);
mLastPoint1.set(mCurrentPoint1);
mLastPoint2.set(mCurrentPoint2);
}
/**
* 获取两点间距离
*/
private float distance(PointF point1, PointF point2) {
float dx = point2.x - point1.x;
float dy = point2.y - point1.y;
return (float) Math.sqrt(dx * dx + dy * dy);
}
/**
* 根据当前图片旋转的角度,判断是否回弹
*/
private void checkRotation() {
float currentDegree = getCurrentRotateDegree();
float degree = currentDegree;
// 根据当前图片旋转的角度值所在区间,判断要转到几度
degree = Math.abs(degree);
if (degree > 45 && degree <= 135) {
degree = 90;
} else if (degree > 135 && degree <= 225) {
degree = 180;
} else if (degree > 225 && degree <= 315) {
degree = 270;
} else {
degree = 0;
}
// 判断顺时针还是逆时针旋转
degree = currentDegree < 0 ? -degree : degree;
mMatrix.postRotate(degree - currentDegree, mImageRect.centerX(), mImageRect.centerY());
}
private float[] xAxis = new float[]{1f, 0f}; // 表示与x轴同方向的向量
/**
* 获取当前图片旋转角度
*
* @return 图片当前的旋转角度
*/
private float getCurrentRotateDegree() {
// 每次重置初始向量的值为与x轴同向
xAxis[0] = 1f;
xAxis[1] = 0f;
// 初始向量通过矩阵变换后的向量
mMatrix.mapVectors(xAxis);
// 变换后向量与x轴夹角
double rad = Math.atan2(xAxis[1], xAxis[0]);
return (float) Math.toDegrees(rad);
}
/**
* 检查图片缩放比例是否超过设置的大小
*/
private void checkScale() {
PointF scaleCenter = getScaleCenter();
float scaleFactor = 1.0f;
// 获取图片当前是水平还是垂直
int imgOrientation = imgOrientation();
// 超过设置的上限或下限则回弹到设置的限制值
// 除以当前图片缩放比例mScaleFactor,postScale()方法执行后的图片的缩放比例即为被除数大小
if (imgOrientation == HORIZONTAL
&& mScaleFactor < mHorizontalMinScaleFactor) {
scaleFactor = mHorizontalMinScaleFactor / mScaleFactor;
} else if (imgOrientation == VERTICAL
&& mScaleFactor < mVerticalMinScaleFactor) {
scaleFactor = mVerticalMinScaleFactor / mScaleFactor;
} else if (mScaleFactor > mMaxScaleFactor) {
scaleFactor = mMaxScaleFactor / mScaleFactor;
}
mMatrix.postScale(scaleFactor, scaleFactor, scaleCenter.x, scaleCenter.y);
mScaleFactor *= scaleFactor;
}
private static final int HORIZONTAL = 0;
private static final int VERTICAL = 1;
/**
* 判断图片当前是水平还是垂直
*
* @return 水平则返回 {@code HORIZONTAL},垂直则返回 {@code VERTICAL}
*/
private int imgOrientation() {
float degree = Math.abs(getCurrentRotateDegree());
int orientation = HORIZONTAL;
if (degree > 45f && degree <= 135f) {
orientation = VERTICAL;
}
return orientation;
}
/**
* 将图片移回控件中心
*/
private void checkBorder() {
// 由于旋转回弹与缩放回弹会影响图片所在位置,所以此处需要更新ImageRect的值
refreshImageRect();
// 默认不移动
float dx = 0f;
float dy = 0f;
// mImageRect中的坐标值为相对View的值
// 图片宽大于控件时图片与控件之间不能有白边
if (mImageRect.width() > getWidth()) {
if (mImageRect.left > 0) {/*判断图片左边界与控件之间是否有空隙*/
dx = -mImageRect.left;
} else if (mImageRect.right < getWidth()) {/*判断图片右边界与控件之间是否有空隙*/
dx = getWidth() - mImageRect.right;
}
} else {/*宽小于控件则移动到中心*/
dx = getWidth() / 2 - mImageRect.centerX();
}
// 图片高大于控件时图片与控件之间不能有白边
if (mImageRect.height() > getHeight()) {
if (mImageRect.top > 0) {/*判断图片上边界与控件之间是否有空隙*/
dy = -mImageRect.top;
} else if (mImageRect.bottom < getHeight()) {/*判断图片下边界与控件之间是否有空隙*/
dy = getHeight() - mImageRect.bottom;
}
} else {/*高小于控件则移动到中心*/
dy = getHeight() / 2 - mImageRect.centerY();
}
mMatrix.postTranslate(dx, dy);
}
/**
* 更新图片所在区域,并将矩阵应用到图片
*/
protected void applyMatrix() {
refreshImageRect(); /*将矩阵映射到ImageRect*/
setImageMatrix(mMatrix);
}
/**
* 图片使用矩阵变换后,刷新图片所对应的mImageRect所指示的区域
*/
private void refreshImageRect() {
if (getDrawable() != null) {
mImageRect.set(getDrawable().getBounds());
mMatrix.mapRect(mImageRect, mImageRect);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mRevertAnimator.cancel();
}
//-----Aninmator-------------------
/**
* 图片回弹动画
*/
private class MatrixRevertAnimator extends ValueAnimator
implements ValueAnimator.AnimatorUpdateListener {
private float[] mFromMatrixValue; // 动画初始时矩阵值
private float[] mToMatrixValue; // 动画终结时矩阵值
private float[] mInterpolateMatrixValue; // 动画执行过程中矩阵值
MatrixRevertAnimator() {
mInterpolateMatrixValue = new float[9];
setFloatValues(0f, 1f);
addUpdateListener(this);
}
void setMatrixValue(float[] fromMatrixValue, final float[] toMatrixValue) {
mFromMatrixValue = fromMatrixValue;
mToMatrixValue = toMatrixValue;
addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mMatrix.setValues(toMatrixValue);
applyMatrix();
}
});
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (mFromMatrixValue != null
&& mToMatrixValue != null && mInterpolateMatrixValue != null) {
// 根据动画当前进度设置矩阵的值
for (int i = 0; i < 9; i++) {
float animatedValue = (float) animation.getAnimatedValue();
mInterpolateMatrixValue[i] = mFromMatrixValue[i]
+ (mToMatrixValue[i] - mFromMatrixValue[i]) * animatedValue;
}
mMatrix.setValues(mInterpolateMatrixValue);
applyMatrix();
}
}
}
//-------getter and setter---------
public void setmMaxScaleFactor(float mMaxScaleFactor) {
this.mMaxScaleFactor = mMaxScaleFactor;
}
public void setmMinScaleFactor(float mMinScaleFactor) {
this.mMinScaleFactor = mMinScaleFactor;
}
public void setOpenScale(boolean openScale) {
this.mOpenScale = openScale;
}
public void setOpenTranslate(boolean openTranslate) {
this.mOpenTranslate = openTranslate;
}
public void setOpenRotate(boolean openRotate) {
this.mOpenRotate = openRotate;
}
public void setOpenBorder(boolean openBorder) {
this.mOpenBorder = openBorder;
invalidate();
}
public boolean openBorder() {
return this.mOpenBorder;
}
}
package com.mints.wisdomclean.ui.widgets;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
public class WaveView extends View {
private int width = 0;
private int height = 0;
private int baseLine = 0;// 基线,用于控制水位上涨的,这里是写死了没动,你可以不断的设置改变。
private Paint mPaint;
private Paint mCirclePaint;
private TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private int waveHeight = 26;// 波浪的最高度
private int waveWidth;//波长
private float offset = 0f;//偏移量
private String mForegroundColor = "#F61F03";
private String mProgressStr = "72%";
private Path mCirclePath;
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public void setProgressStr(String progressStr) {
this.mProgressStr = progressStr;
invalidate();
}
public void setForegroundColor(String foregroundColor) {
this.mForegroundColor = foregroundColor;
invalidate();
}
/**
* 不断的更新偏移量,并且循环。
*/
private void updateXControl() {
//设置一个波长的偏移
ValueAnimator mAnimator = ValueAnimator.ofFloat(0, waveWidth);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(animation -> {
float animatorValue = (float) animation.getAnimatedValue();
offset = animatorValue;//不断的设置偏移量,并重画
postInvalidate();
});
mAnimator.setDuration(1000);
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(Color.WHITE);
canvas.drawPath(mCirclePath, mPaint);
canvas.save();
canvas.clipPath(mCirclePath);
mPaint.setColor(Color.WHITE);
canvas.drawPath(getPath(), mPaint);
mPaint.setColor(Color.parseColor(mForegroundColor));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(getPath(), mPaint);
mCirclePaint.setColor(Color.parseColor(mForegroundColor));
canvas.drawCircle(waveWidth / 2, waveWidth / 2, waveWidth / 2 - 2f, mCirclePaint);
canvas.drawText(mProgressStr, waveWidth / 2, waveWidth / 2 + 10, mTextPaint);
canvas.restore();
}
//初始化paint,没什么可说的。
private void initView() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mCirclePaint = new Paint();
mCirclePaint.setStrokeWidth(4f);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setAntiAlias(true);
mCirclePath = new Path();
mTextPaint.setStrokeWidth(1f);
mTextPaint.setTextSize(dp2px(getContext(), 12f));
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(Color.BLACK);
mTextPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = getMeasuredWidth();//获取屏幕宽度
height = getMeasuredHeight();//获取屏幕高度
waveWidth = width;
baseLine = height / 2 - 10;
updateXControl();
int circleRadius = height < width ? height / 2 : width / 2;
mCirclePath.addCircle(width / 2, height / 2, circleRadius, Path.Direction.CCW);
}
/**
* 核心代码,计算path
*
* @return
*/
private Path getPath() {
int itemWidth = waveWidth / 2;//半个波长
Path mPath = new Path();
mPath.moveTo(-itemWidth * 3, baseLine);//起始坐标
//核心的代码就是这里
for (int i = -3; i < 2; i++) {
int startX = i * itemWidth;
mPath.quadTo(
startX + itemWidth / 2 + offset,//控制点的X,(起始点X + itemWidth/2 + offset)
getWaveHeight(i),//控制点的Y
startX + itemWidth + offset,//结束点的X
baseLine//结束点的Y
);//只需要处理完半个波长,剩下的有for循环自已就添加了。
}
//下面这三句话很重要,它是形成了一封闭区间,让曲线以下的面积填充一种颜色,大家可以把这3句话注释了看看效果。
mPath.lineTo(width, height);
mPath.lineTo(0, height);
mPath.close();
return mPath;
}
//奇数峰值是正的,偶数峰值是负数
private int getWaveHeight(int num) {
if (num % 2 == 0) {
return baseLine + waveHeight;
}
return baseLine - waveHeight;
}
private float dp2px(Context context, Float pxVal) {
return pxVal * context.getResources().getDisplayMetrics().scaledDensity + 0.5f;
}
}
\ No newline at end of file
package com.mints.wisdomclean.ui.widgets.applock;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.view.View;
import com.mints.wisdomclean.utils.BubbleUtils;
@SuppressLint("ViewConstructor")
public class GestureView extends View {
private static final String TAG = GestureView.class.getName();
/**
* GestureLockView的三种状态
*/
enum Mode {
STATUS_NO_FINGER, STATUS_FINGER_ON, STATUS_FINGER_UP;
}
/**
* GestureLockView的当前状态
*/
private Mode mCurrentStatus = Mode.STATUS_NO_FINGER;
/**
* 宽度
*/
private int mWidth;
/**
* 高度
*/
private int mHeight;
/**
* 外圆半径
*/
private int mRadius;
/**
* 画笔的宽度
*/
private int mStrokeWidth = 2;
/**
* 圆心坐标
*/
private int mCenterX;
private int mCenterY;
private Paint mPaint;
/**
* 箭头(小三角最长边的一半长度 = mArrawRate * mWidth / 2 )
*/
private float mArrowRate = 0.333f;
private int mArrowDegree = -1;
private Path mArrowPath;
/**
* 内圆的半径 = mInnerCircleRadiusRate * mRadus
*/
private float mInnerCircleRadiusRate = 0.25f;
/**
* 四个颜色,可由用户自定义,初始化时由GestureLockViewGroup传入
*/
private int mColorNoFingerInner;
private int mColorNoFingerOutter;
private int mColorFingerOn;
private int mColorFingerUpMatched;
private int mColorFingerUpNotMatched;
private boolean matched;
public GestureView(Context context, int colorNoFingerInner, int colorNoFingerOutter, int colorFingerOn, int colorFingerUpMatched, int colorFingerUpNotMatched) {
super(context);
this.mColorNoFingerInner = colorNoFingerInner;
this.mColorNoFingerOutter = colorNoFingerOutter;
this.mColorFingerOn = colorFingerOn;
this.mColorFingerUpMatched = colorFingerUpMatched;
this.mColorFingerUpNotMatched = colorFingerUpNotMatched;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mArrowPath = new Path();
mStrokeWidth = BubbleUtils.dp2px(1);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
// 取长和宽中的小值
mWidth = Math.min(mWidth, mHeight);
mCenterX = mCenterY = mWidth / 2;
mRadius = mWidth / 3;
mRadius -= mStrokeWidth / 2;
// 绘制三角形,初始时是个默认箭头朝上的一个等腰三角形,用户绘制结束后,根据由两个GestureLockView决定需要旋转多少度
float mArrowLength = mWidth / 2 * mArrowRate;
mArrowPath.moveTo(mWidth / 2, mStrokeWidth + 2);
mArrowPath.lineTo(mWidth / 2 - mArrowLength, mStrokeWidth + 2
+ mArrowLength);
mArrowPath.lineTo(mWidth / 2 + mArrowLength, mStrokeWidth + 2
+ mArrowLength);
mArrowPath.close();
mArrowPath.setFillType(Path.FillType.WINDING);
}
@Override
protected void onDraw(Canvas canvas) {
switch (mCurrentStatus) {
case STATUS_FINGER_ON:
// 绘制外圆
mPaint.setStyle(Style.FILL); //
mPaint.setColor(0x33ffffff);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 绘制外圆环
mPaint.setStyle(Style.STROKE);
mPaint.setColor(0x99fdfdfd);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 绘制内圆
mPaint.setStyle(Style.FILL);
mPaint.setColor(mColorFingerOn);
canvas.drawCircle(mCenterX, mCenterY, mRadius
* mInnerCircleRadiusRate, mPaint);
break;
case STATUS_FINGER_UP:
if (matched) {
// 绘制外圆环
mPaint.setColor(mColorFingerUpMatched);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
} else {
// 外圆
mPaint.setStyle(Style.FILL);
mPaint.setColor(0x33ff5c5c);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 外圆环
mPaint.setColor(0x99e3362d);
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
mPaint.setColor(mColorFingerUpNotMatched);
}
// 绘制内圆
mPaint.setStyle(Style.FILL);
canvas.drawCircle(mCenterX, mCenterY, mRadius
* mInnerCircleRadiusRate, mPaint);
// drawArrow(canvas);
break;
case STATUS_NO_FINGER:
// 绘制外圆
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setColor(mColorNoFingerOutter);
canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint);
// 绘制内圆
mPaint.setStyle(Style.FILL);
mPaint.setColor(mColorNoFingerInner);
canvas.drawCircle(mCenterX, mCenterY, mRadius
* mInnerCircleRadiusRate, mPaint);
break;
}
}
/**
* 绘制箭头
*
* @param canvas
*/
private void drawArrow(Canvas canvas) {
if (mArrowDegree != -1) {
mPaint.setStyle(Style.FILL);
canvas.save();
canvas.rotate(mArrowDegree, mCenterX, mCenterY);
canvas.drawPath(mArrowPath, mPaint);
canvas.restore();
}
}
public void setMatched(boolean matched) {
this.matched = matched;
}
/**
* 设置当前模式并重绘界面
*
* @param mode
*/
public void setMode(Mode mode) {
this.mCurrentStatus = mode;
invalidate();
}
public void setArrowDegree(int degree) {
this.mArrowDegree = degree;
}
public int getArrowDegree() {
return this.mArrowDegree;
}
}
package com.mints.wisdomclean.ui.widgets.applock;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import com.mints.wisdomclean.R;
import com.mints.wisdomclean.utils.BubbleUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 整体包含n*n个GestureLockView,每个GestureLockView间间隔mMarginBetweenLockView,
* 最外层的GestureLockView与容器存在mMarginBetweenLockView的外边距
*
* 关于GestureLockView的边长(n*n): n * mGestureLockViewWidth + ( n + 1 ) *
* mMarginBetweenLockView = mWidth ; 得:mGestureLockViewWidth = 4 * mWidth / ( 5
* * mCount + 1 ) 注:mMarginBetweenLockView = mGestureLockViewWidth * 0.25 ;
*
* @author
*
*/
public class GestureViewGroup extends RelativeLayout {
private static final String TAG = GestureViewGroup.class.getName();
/**
* 保存所有的GestureLockView
*/
private GestureView[] mGestureLockViews;
/**
* 每个边上的GestureLockView的个数
*/
private int mCount = 3;
/**
* 存储答案
*/
private int[] mAnswer = new int[0];
/**
* 保存用户选中的GestureLockView的id
*/
private final List<Integer> mChoose = new ArrayList<>();
private Paint mPaint;
/**
* 每个GestureLockView中间的间距 设置为:mGestureLockViewWidth * 25%
*/
private int mMarginBetweenLockView = 0;
/**
* GestureLockView的边长 4 * mWidth / ( 5 * mCount + 1 )
*/
private int mGestureLockViewWidth = 0;
/**
* GestureLockView无手指触摸的状态下内圆的颜色
*/
private int mNoFingerInnerCircleColor = 0xffffffff;
/**
* GestureLockView无手指触摸的状态下外圆的颜色
*/
private int mNoFingerOuterCircleColor = 0xfffdfdfd;
/**
* GestureLockView手指触摸的状态下内圆和外圆的颜色
*/
private int mFingerOnColor = 0xffffffff;
/**
* GestureLockView手指抬起的状态下内圆和外圆的颜色
*/
private int mFingerUpMatchedColor = 0xffffffff;
private int mFingerUpNotMatchedColor = 0xffe3362d;
/**
* 宽度
*/
private int mWidth;
/**
* 高度
*/
private int mHeight;
private Path mPath;
/**
* 指引线的开始位置x
*/
private int mLastPathX;
/**
* 指引线的开始位置y
*/
private int mLastPathY;
/**
* 指引下的结束位置
*/
private final Point mTmpTarget = new Point();
/**
* 最大尝试次数
*/
private int mTryTimes = 4;
/**
* 回调接口
*/
private OnGestureViewListener mOnGestureLockViewListener;
// set password or verify password, verify password: if not matched, show red color; set password: first,show green, second(confirm the first ),show red if not match first
private boolean setPasswordMode = false;
private int inputCorrectPasswordCount = 0;
public GestureViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GestureViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
/**
* 获得所有自定义的参数的值
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GestureLockViewGroup, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.GestureLockViewGroup_color_no_finger_inner_circle:
mNoFingerInnerCircleColor = a.getColor(attr, mNoFingerInnerCircleColor);
break;
case R.styleable.GestureLockViewGroup_color_no_finger_outer_circle:
mNoFingerOuterCircleColor = a.getColor(attr, mNoFingerOuterCircleColor);
break;
case R.styleable.GestureLockViewGroup_outer_circle_width:
mGestureLockViewWidth = (int)a.getDimension(attr, mGestureLockViewWidth);
break;
case R.styleable.GestureLockViewGroup_margin_between_vircle:
mMarginBetweenLockView = (int)a.getDimension(attr, mMarginBetweenLockView);
break;
case R.styleable.GestureLockViewGroup_color_finger_on:
mFingerOnColor = a.getColor(attr, mFingerOnColor);
break;
case R.styleable.GestureLockViewGroup_color_finger_up:
mFingerUpMatchedColor = a.getColor(attr, mFingerUpMatchedColor);
break;
case R.styleable.GestureLockViewGroup_count:
mCount = a.getInt(attr, 3);
break;
case R.styleable.GestureLockViewGroup_tryTimes:
mTryTimes = a.getInt(attr, 5);
default:
break;
}
}
a.recycle();
// 初始化画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(BubbleUtils.dp2px(3));
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
// mPaint.setColor(Color.parseColor("#aaffffff"));
mPath = new Path();
setPasswordMode = false;
inputCorrectPasswordCount = 0;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
mHeight = mWidth = mWidth < mHeight ? mWidth : mHeight;
// setMeasuredDimension(mWidth, mHeight);
// 初始化mGestureLockViews
if (mGestureLockViews == null) {
mGestureLockViews = new GestureView[mCount * mCount];
// 计算每个GestureLockView的宽度
if(mGestureLockViewWidth == 0) {
// mGestureLockViewWidth = (int) (4 * mWidth * 1.0f / (5 * mCount + 1));
mGestureLockViewWidth = (int) ( mWidth * 1.0f / 4f);
}
// 计算每个GestureLockView的间距
if(mMarginBetweenLockView == 0) {
// mMarginBetweenLockView = mGestureLockViewWidth / 4;
mMarginBetweenLockView = mWidth / 8;
}
// 设置画笔的宽度为GestureLockView的内圆直径稍微小点(不喜欢的话,随便设)
// mPaint.setStrokeWidth(mGestureLockViewWidth * 0.29f);
for (int i = 0; i < mGestureLockViews.length; i++) {
// 初始化每个GestureLockView
mGestureLockViews[i] = new GestureView(getContext(), mNoFingerInnerCircleColor,
mNoFingerOuterCircleColor, mFingerOnColor, mFingerUpMatchedColor, mFingerUpNotMatchedColor);
mGestureLockViews[i].setId(i + 1);
// 设置参数,主要是定位GestureLockView间的位置
LayoutParams lockerParams = new LayoutParams(mGestureLockViewWidth, mGestureLockViewWidth);
// 不是每行的第一个,则设置位置为前一个的右边
if (i % mCount != 0) {
lockerParams.addRule(RelativeLayout.RIGHT_OF, mGestureLockViews[i - 1].getId());
}
// 从第二行开始,设置为上一行同一位置View的下面
if (i > mCount - 1) {
lockerParams.addRule(RelativeLayout.BELOW, mGestureLockViews[i - mCount].getId());
}
// 设置右下左上的边距
int rightMargin = 0;
int bottomMargin = 0;
int leftMagin = mMarginBetweenLockView;
int topMargin = mMarginBetweenLockView;
/**
* 每个View都有右外边距和底外边距 第一行的有上外边距 第一列的有左外边距
*/
if (i >= 0 && i < mCount)// 第一行
{
topMargin = 0;
}
if (i % mCount == 0)// 第一列
{
leftMagin = 0;
}
lockerParams.setMargins(leftMagin, topMargin, rightMargin, bottomMargin);
mGestureLockViews[i].setMode(GestureView.Mode.STATUS_NO_FINGER);
addView(mGestureLockViews[i], lockerParams);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 重置
reset();
// going through
case MotionEvent.ACTION_MOVE:
mPaint.setColor(mFingerOnColor);
mPaint.setAlpha(50);
GestureView child = getChildIdByPos(x, y);
if (child != null) {
int cId = child.getId();
if (!mChoose.contains(cId)) {
mChoose.add(cId);
child.setMode( GestureView.Mode.STATUS_FINGER_ON);
if (mOnGestureLockViewListener != null)
mOnGestureLockViewListener.onBlockSelected(cId);
// 设置指引线的起点
mLastPathX = child.getLeft() / 2 + child.getRight() / 2;
mLastPathY = child.getTop() / 2 + child.getBottom() / 2;
if (mChoose.size() == 1)// 当前添加为第一个
{
mPath.moveTo(mLastPathX, mLastPathY);
} else
// 非第一个,将两者使用线连上
{
mPath.lineTo(mLastPathX, mLastPathY);
}
}
}
// 指引线的终点
mTmpTarget.x = x;
mTmpTarget.y = y;
break;
case MotionEvent.ACTION_UP:
// first input password and >= 4 dot
// Log.e(TAG, "inputCorrectPasswordCount:"+inputCorrectPasswordCount);
boolean fistInputCorrectPassword = setPasswordMode && ((inputCorrectPasswordCount & 1)==0) && mChoose.size() >= 4;
//if first input dot >=4 or the second input(confirm step)
if(fistInputCorrectPassword ||(setPasswordMode && ((inputCorrectPasswordCount & 1)==1))){
inputCorrectPasswordCount++;
}
boolean matched = checkAnswer();
//for confirm step, if matched is false, the fistInputCorrectPassword is always false
matched = matched || fistInputCorrectPassword;
for (int i = 0; i < mGestureLockViews.length; i++) {
// 初始化每个GestureLockView
mGestureLockViews[i].setMatched(matched);
}
if (matched) {
mPaint.setColor(0x99ffffff);
} else {
mPaint.setColor(0x99ff352b);
}
mPaint.setAlpha(50);
this.mTryTimes--;
// 回调是否成功
if (mOnGestureLockViewListener != null && mChoose.size() > 0) {
mOnGestureLockViewListener.onGestureEvent(matched);
if (this.mTryTimes == 0) {
mOnGestureLockViewListener.onUnmatchedExceedBoundary();
}
}
// Log.e(TAG, "mUnMatchExceedBoundary = " + mTryTimes);
// Log.e(TAG, "mChoose = " + mChoose);
// 将终点设置位置为起点,即取消指引线
mTmpTarget.x = mLastPathX;
mTmpTarget.y = mLastPathY;
// 改变子元素的状态为UP
changeItemMode();
// 计算每个元素中箭头需要旋转的角度
for (int i = 0; i + 1 < mChoose.size(); i++) {
int childId = mChoose.get(i);
int nextChildId = mChoose.get(i + 1);
GestureView startChild = (GestureView) findViewById(childId);
GestureView nextChild = (GestureView) findViewById(nextChildId);
int dx = nextChild.getLeft() - startChild.getLeft();
int dy = nextChild.getTop() - startChild.getTop();
// 计算角度
int angle = (int) Math.toDegrees(Math.atan2(dy, dx)) + 90;
startChild.setArrowDegree(angle);
}
break;
}
invalidate();
return true;
}
private void changeItemMode() {
for ( GestureView gestureLockView : mGestureLockViews) {
if (mChoose.contains(gestureLockView.getId())) {
gestureLockView.setMode( GestureView.Mode.STATUS_FINGER_UP);
}
}
}
/**
*
* 做一些必要的重置
*/
public void reset() {
mChoose.clear();
mPath.reset();
if (mGestureLockViews != null) {
for ( GestureView gestureLockView : mGestureLockViews) {
gestureLockView.setMode( GestureView.Mode.STATUS_NO_FINGER);
gestureLockView.setArrowDegree(-1);
}
}
invalidate();
}
public void setGestureLockSet(boolean setPasswordMode){
this.setPasswordMode = setPasswordMode;
}
/**
* 指定时间去清除绘制的状态
*
* @param delayTime
* 延迟执行时间
*/
public void clear(long delayTime) {
new Handler().postDelayed(new clearStateRunnable(), delayTime);
}
public void clear() {
new Handler().postDelayed(new clearStateRunnable(), 200);
}
/**
* 清除绘制状态的线程
*/
final class clearStateRunnable implements Runnable {
public void run() {
reset();
}
}
/**
* 检查用户绘制的手势是否正确
*
* @return
*/
private boolean checkAnswer() {
if (mAnswer.length != mChoose.size())
return false;
for (int i = 0; i < mAnswer.length; i++) {
if (mAnswer[i] != mChoose.get(i))
return false;
}
return true;
}
/**
* 检查当前左边是否在child中
*
* @param child
* @param x
* @param y
* @return
*/
private boolean checkPositionInChild(View child, int x, int y) {
// 设置了内边距,即x,y必须落入下GestureLockView的内部中间的小区域中,可以通过调整padding使得x,y落入范围不变大,或者不设置padding
int padding = mChoose.isEmpty() ? 0 : (int) (mGestureLockViewWidth * 0.15);
if (x >= child.getLeft() + padding && x <= child.getRight() - padding && y >= child.getTop() + padding
&& y <= child.getBottom() - padding) {
return true;
}
return false;
}
/**
* 通过x,y获得落入的GestureLockView
*
* @param x
* @param y
* @return
*/
private GestureView getChildIdByPos(int x, int y) {
for ( GestureView gestureLockView : mGestureLockViews) {
if (checkPositionInChild(gestureLockView, x, y)) {
return gestureLockView;
}
}
return null;
}
/**
* 设置回调接口
*
* @param listener
*/
public void setOnGestureLockViewListener(OnGestureViewListener listener) {
this.mOnGestureLockViewListener = listener;
}
/*
*
* get the set password
*/
public int[] getAnswer() {
int[] answer = new int[mChoose.size()];
for (int i = 0; i < mChoose.size(); i++) {
answer[i] = mChoose.get(i);
}
return answer;
}
/**
* 对外公布设置答案的方法
*
* @param answer
*/
public void setAnswer(int[] answer) {
if (answer == null) {
this.mAnswer = new int[0];
} else {
this.mAnswer = answer;
}
}
/**
* 设置最大实验次数
*
* @param boundary
*/
public void setUnMatchExceedBoundary(int boundary) {
this.mTryTimes = boundary;
}
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// 绘制GestureLockView间的连线
if (mPath != null) {
canvas.drawPath(mPath, mPaint);
}
// 绘制指引线
if (mChoose.size() > 0) {
if (mLastPathX != 0 && mLastPathY != 0)
canvas.drawLine(mLastPathX, mLastPathY, mTmpTarget.x, mTmpTarget.y, mPaint);
}
}
public interface OnGestureViewListener {
/**
* 单独选中元素的Id
*
* @param cId
*/
public void onBlockSelected(int cId);
/**
* 是否匹配
*
* @param matched
*/
public void onGestureEvent(boolean matched);
/**
* 超过尝试次数
*/
public void onUnmatchedExceedBoundary();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment