feat: 初始化项目结构和基本功能

- 创建项目根目录和主要子模块
- 添加基本的 Activity 和布局文件
- 实现简单的导航和电话拨打功能
- 添加相机和图像处理相关代码
- 创建网络请求和数据加密工具类
- 设置 AndroidManifest 文件和权限
This commit is contained in:
songzhiling
2025-04-11 11:52:07 +08:00
parent 74ce8fc526
commit 91305ab9d1
386 changed files with 36517 additions and 34 deletions

View File

@ -0,0 +1,76 @@
package com.za.signature;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.za.servicing.R;
import com.za.signature.util.StatusBarCompat;
/***
* Activity基类
*
* @since 2018-06-25
* @author king
*/
public abstract class BaseActivity extends AppCompatActivity {
protected View actionbar;
protected TextView tvCancel;
protected TextView tvSave;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
initTitleBar();
initView();
initData();
}
/**
* 初始化标题栏
*/
protected void initTitleBar() {
actionbar = findViewById(R.id.actionbar);
tvCancel = findViewById(R.id.tv_cancel);
tvSave = findViewById(R.id.tv_ok);
}
/**
* 获取布局
*/
protected abstract int getLayout();
/**
* 初始化视图
*/
protected abstract void initView();
/**
* 初始化数据
*/
protected abstract void initData();
/**
* 设置主题颜色
*
* @param color 主题颜色
*/
protected void setThemeColor(int color) {
try {
if (actionbar != null) {
actionbar.setBackgroundColor(color);
}
StatusBarCompat.compat(this, color);
} catch (Exception e) {
}
}
}

View File

@ -0,0 +1,441 @@
package com.za.signature;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.util.BitmapUtil;
import com.za.signature.util.DisplayUtil;
import com.za.signature.util.SystemUtil;
import com.za.signature.view.CircleImageView;
import com.za.signature.view.CircleView;
import com.za.signature.view.GridDrawable;
import com.za.signature.view.GridPaintView;
import com.za.signature.view.HVScrollView;
import com.za.signature.view.HandWriteEditView;
import com.za.signature.view.PaintSettingWindow;
/***
* 名称GridWriteActivity<br>
* 描述:米格输入界面
* 最近修改时间:
* @since 2017/11/14
* @author king
*/
public class GridPaintActivity extends BaseActivity implements View.OnClickListener, Handler.Callback {
private View mCircleContainer;
private HVScrollView mTextContainer;
private HandWriteEditView mEditView;
private CircleImageView mDeleteView;
private CircleImageView mSpaceView;
private CircleImageView mClearView;
private CircleImageView mEnterView;
private CircleView mPenCircleView;
private GridPaintView mPaintView;
private ProgressDialog mSaveProgressDlg;
private static final int MSG_SAVE_SUCCESS = 1;
private static final int MSG_SAVE_FAILED = 2;
private static final int MSG_WRITE_OK = 100;
private Handler mHandler;
private String mSavePath;
private Editable cacheEditable;
private int bgColor;
private boolean isCrop;
private String format;
private int lineSize;
private int fontSize;
private PaintSettingWindow settingWindow;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
bgColor = getIntent().getIntExtra("background", Color.TRANSPARENT);
lineSize = getIntent().getIntExtra("lineLength", 15);
fontSize = getIntent().getIntExtra("fontSize", 50);
isCrop = getIntent().getBooleanExtra("crop", false);
format = getIntent().getStringExtra("format");
PenConfig.PAINT_COLOR = PenConfig.getPaintColor(this);
PenConfig.PAINT_SIZE_LEVEL = PenConfig.getPaintTextLevel(this);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
super.onCreate(savedInstanceState);
SystemUtil.disableShowInput(getApplicationContext(), mEditView);
}
/**
* 横竖屏切换
*/
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.sign_activity_grid_paint);
initTitleBar();
initView();
initData();
SystemUtil.disableShowInput(getApplicationContext(), mEditView);
if (mEditView != null && cacheEditable != null) {
mEditView.setText(cacheEditable);
mEditView.setSelection(cacheEditable.length());
mEditView.requestFocus();
}
mHandler = new Handler(this);
if (settingWindow != null) {
settingWindow.dismiss();
}
}
@Override
protected int getLayout() {
return R.layout.sign_activity_grid_paint;
}
@Override
protected void initData() {
setThemeColor(PenConfig.THEME_COLOR);
mPenCircleView.setOutBorderColor(PenConfig.THEME_COLOR);
mClearView.setEnabled(false);
mClearView.setImage(R.mipmap.sign_ic_clear, Color.LTGRAY);
mEnterView.setImage(R.mipmap.sign_ic_enter, PenConfig.THEME_COLOR);
mSpaceView.setImage(R.mipmap.sign_ic_space, PenConfig.THEME_COLOR);
mDeleteView.setImage(R.mipmap.sign_ic_delete, PenConfig.THEME_COLOR);
mHandler = new Handler(this);
}
/**
* 初始化视图
*/
@Override
protected void initView() {
mPaintView = findViewById(R.id.paint_view);
mDeleteView = findViewById(R.id.delete);
mSpaceView = findViewById(R.id.space);
mPenCircleView = findViewById(R.id.pen_color);
mClearView = findViewById(R.id.clear);
mEnterView = findViewById(R.id.enter);
mEditView = findViewById(R.id.et_view);
mTextContainer = findViewById(R.id.sv_container);
mCircleContainer = findViewById(R.id.circle_container);
mEnterView.setOnClickListener(this);
mDeleteView.setOnClickListener(this);
mSpaceView.setOnClickListener(this);
mPenCircleView.setOnClickListener(this);
tvCancel.setOnClickListener(this);
mClearView.setOnClickListener(this);
tvSave.setOnClickListener(this);
mPenCircleView.setPaintColor(PenConfig.PAINT_COLOR);
mPenCircleView.setRadiusLevel(PenConfig.PAINT_SIZE_LEVEL);
int size = getResources().getDimensionPixelSize(R.dimen.sign_grid_size);
GridDrawable gridDrawable = new GridDrawable(size, size, Color.WHITE);
mPaintView.setBackground(gridDrawable);
mPaintView.setGetTimeListener(new GridPaintView.WriteListener() {
@Override
public void onWriteStart() {
mHandler.removeMessages(MSG_WRITE_OK);
}
@Override
public void onWriteCompleted(long time) {
mHandler.sendEmptyMessageDelayed(MSG_WRITE_OK, 1000);
}
});
int maxWidth = lineSize * DisplayUtil.dip2px(this, fontSize);
if (!isCrop) {
mEditView.setWidth(maxWidth + 2);
mEditView.setMaxWidth(maxWidth);
} else {
mEditView.setWidth(maxWidth * 2 / 3);
mEditView.setMaxWidth(maxWidth * 2 / 3);
}
mEditView.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize);
mEditView.setLineHeight(DisplayUtil.dip2px(this, fontSize));
mEditView.setHorizontallyScrolling(false);
mEditView.requestFocus();
if (bgColor != Color.TRANSPARENT) {
mTextContainer.setBackgroundColor(bgColor);
}
mEditView.addTextWatcher(s -> {
if (s != null && s.length() > 0) {
mClearView.setEnabled(true);
mClearView.setImage(R.mipmap.sign_ic_clear, PenConfig.THEME_COLOR);
} else {
mClearView.setEnabled(false);
mClearView.setImage(R.mipmap.sign_ic_clear, Color.LTGRAY);
}
});
}
@Override
protected void onStop() {
super.onStop();
mHandler.removeMessages(MSG_WRITE_OK);
}
@Override
protected void onDestroy() {
mPaintView.release();
super.onDestroy();
mHandler.removeMessages(MSG_SAVE_FAILED);
mHandler.removeMessages(MSG_SAVE_SUCCESS);
}
private void initSaveProgressDlg() {
mSaveProgressDlg = new ProgressDialog(this);
mSaveProgressDlg.setMessage("正在保存,请稍候...");
mSaveProgressDlg.setCancelable(false);
}
/**
* 弹出清空提示
*/
private void showClearTips() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("清空文本框内手写内容?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) -> {
mEditView.setText("");
mEditView.setSelection(0);
cacheEditable = null;
});
builder.show();
}
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_SAVE_FAILED:
if (mSaveProgressDlg != null) {
mSaveProgressDlg.dismiss();
}
Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_SHORT).show();
break;
case MSG_SAVE_SUCCESS:
if (mSaveProgressDlg != null) {
mSaveProgressDlg.dismiss();
}
Intent intent = new Intent();
intent.putExtra(PenConfig.SAVE_PATH, mSavePath);
setResult(RESULT_OK, intent);
finish();
break;
case MSG_WRITE_OK:
if (!mPaintView.isEmpty()) {
Bitmap bitmap = mPaintView.buildBitmap(isCrop, DisplayUtil.dip2px(GridPaintActivity.this, fontSize));
this.cacheEditable = mEditView.addBitmapToText(bitmap);
mPaintView.reset();
}
break;
default:
break;
}
return true;
}
/**
* 保存
*/
private void save() {
if (mEditView.getText() == null || mEditView.getText().length() == 0) {
Toast.makeText(getApplicationContext(), "没有写入任何文字", Toast.LENGTH_SHORT).show();
return;
}
//先检查是否有存储权限
// if (ContextCompat.checkSelfPermission(this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(getApplicationContext(), "没有读写存储的权限", Toast.LENGTH_SHORT).show();
// return;
// }
if (mSaveProgressDlg == null) {
initSaveProgressDlg();
}
mEditView.clearFocus();
mEditView.setCursorVisible(false);
mSaveProgressDlg.show();
new Thread(() -> {
if (PenConfig.FORMAT_JPG.equals(format) && bgColor == Color.TRANSPARENT) {
bgColor = Color.WHITE;
}
Bitmap bm = getWriteBitmap(bgColor);
bm = BitmapUtil.clearBlank(bm, 20, bgColor);
if (bm == null) {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
return;
}
mSavePath = BitmapUtil.saveImage(GridPaintActivity.this, bm, 100, format);
if (mSavePath != null) {
mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget();
} else {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
}
bm.recycle();
}).start();
}
/**
* 获取EdiText截图
*
* @param bgColor 背景颜色
* @return EdiText截图
*/
private Bitmap getWriteBitmap(int bgColor) {
int w = 0;
int h = 0;
for (int i = 0; i < mTextContainer.getChildCount(); i++) {
h += mTextContainer.getChildAt(i).getHeight();
w += mTextContainer.getChildAt(i).getWidth();
}
Bitmap bitmap = null;
try {
bitmap = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_8888);
} catch (OutOfMemoryError e) {
bitmap = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_4444);
}
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(bgColor);
mTextContainer.draw(canvas);
return bitmap;
}
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.delete) {
this.cacheEditable = mEditView.deleteBitmapFromText();
} else if (i == R.id.tv_cancel) {
if (mEditView.getText() != null && mEditView.getText().length() > 0) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
} else if (i == R.id.tv_ok) {
save();
} else if (i == R.id.enter) {
Editable editable = mEditView.getText();
editable.insert(mEditView.getSelectionStart(), "\n");
} else if (i == R.id.space) {
mEditView.addSpace(fontSize);
} else if (i == R.id.clear) {
showClearTips();
} else if (i == R.id.pen_color) {
showSettingWindow();
}
}
/**
* 弹出画笔设置
*/
private void showSettingWindow() {
settingWindow = new PaintSettingWindow(this);
settingWindow.setSettingListener(new PaintSettingWindow.OnSettingListener() {
@Override
public void onColorSetting(int color) {
mPaintView.setPaintColor(color);
mPenCircleView.setPaintColor(PenConfig.PAINT_COLOR);
}
@Override
public void onSizeSetting(int level) {
mPaintView.setPaintWidth(PaintSettingWindow.PEN_SIZES[level]);
mPenCircleView.setRadiusLevel(level);
}
});
int[] location = new int[2];
mCircleContainer.getLocationOnScreen(location);
View contentView = settingWindow.getContentView();
//需要先测量PopupWindow还未弹出时宽高为0
contentView.measure(SystemUtil.makeDropDownMeasureSpec(settingWindow.getWidth()),
SystemUtil.makeDropDownMeasureSpec(settingWindow.getHeight()));
int padding = DisplayUtil.dip2px(this, 10);
int offsetX, offsetY;
Configuration config = getResources().getConfiguration();
int smallestScreenWidth = config.smallestScreenWidthDp;
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && smallestScreenWidth >= 720) {
//平板上横屏显示
settingWindow.popAtBottomRight();
settingWindow.showAsDropDown(mCircleContainer, mCircleContainer.getWidth() - settingWindow.getContentView().getMeasuredWidth() - padding, 10);
} else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
//横屏显示
offsetX = -settingWindow.getContentView().getMeasuredWidth() - padding;
offsetY = -settingWindow.getContentView().getMeasuredHeight() - mCircleContainer.getHeight() / 2 + padding;
settingWindow.popAtLeft();
settingWindow.showAsDropDown(mCircleContainer, offsetX, offsetY);
} else {
//竖屏显示
offsetX = 0;
offsetY = -(settingWindow.getContentView().getMeasuredHeight() + mPenCircleView.getHeight() + 4 * padding);
settingWindow.popAtTopLeft();
settingWindow.showAsDropDown(mPenCircleView, offsetX, offsetY);
}
}
@Override
public void onBackPressed() {
if (mEditView.getText() != null && mEditView.getText().length() > 0) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
* 弹出退出提示
*/
private void showQuitTip() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("当前文字未保存,是否退出?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) -> {
setResult(RESULT_CANCELED);
finish();
});
builder.show();
}
}

View File

@ -0,0 +1,430 @@
package com.za.signature;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.util.BitmapUtil;
import com.za.signature.util.DisplayUtil;
import com.za.signature.util.StatusBarCompat;
import com.za.signature.util.SystemUtil;
import com.za.signature.view.CircleView;
import com.za.signature.view.PaintSettingWindow;
import com.za.signature.view.PaintView;
/**
* 空白手写画板
*
* @author king
* @since 2018/07/10 14:20
*/
public class PaintActivity extends BaseActivity implements View.OnClickListener, PaintView.StepCallback {
/**
* 画布最大宽度
*/
public static final int CANVAS_MAX_WIDTH = 3000;
/**
* 画布最大高度
*/
public static final int CANVAS_MAX_HEIGHT = 3000;
private ImageView mHandView; //切换 滚动/手写
private ImageView mUndoView;
private ImageView mRedoView;
private ImageView mPenView;
private ImageView mClearView;
private CircleView mSettingView;
private PaintView mPaintView;
private ProgressDialog mSaveProgressDlg;
private static final int MSG_SAVE_SUCCESS = 1;
private static final int MSG_SAVE_FAILED = 2;
private String mSavePath;
private boolean hasSize = false;
private float mWidth;
private float mHeight;
private float widthRate = 1.0f;
private float heightRate = 1.0f;
private int bgColor;
private boolean isCrop;
private String format;
private PaintSettingWindow settingWindow;
@Override
protected int getLayout() {
return R.layout.sign_activity_paint;
}
@Override
protected void initView() {
View mCancelView = findViewById(R.id.tv_cancel);
View mOkView = findViewById(R.id.tv_ok);
mPaintView = findViewById(R.id.paint_view);
mHandView = findViewById(R.id.btn_hand);
mUndoView = findViewById(R.id.btn_undo);
mRedoView = findViewById(R.id.btn_redo);
mPenView = findViewById(R.id.btn_pen);
mClearView = findViewById(R.id.btn_clear);
mSettingView = findViewById(R.id.btn_setting);
mUndoView.setOnClickListener(this);
mRedoView.setOnClickListener(this);
mPenView.setOnClickListener(this);
mClearView.setOnClickListener(this);
mSettingView.setOnClickListener(this);
mHandView.setOnClickListener(this);
mCancelView.setOnClickListener(this);
mOkView.setOnClickListener(this);
mPenView.setSelected(true);
mUndoView.setEnabled(false);
mRedoView.setEnabled(false);
mClearView.setEnabled(!mPaintView.isEmpty());
mPaintView.setBackgroundColor(Color.WHITE);
mPaintView.setStepCallback(this);
PenConfig.PAINT_SIZE_LEVEL = PenConfig.getPaintTextLevel(this);
PenConfig.PAINT_COLOR = PenConfig.getPaintColor(this);
mSettingView.setPaintColor(PenConfig.PAINT_COLOR);
mSettingView.setRadiusLevel(PenConfig.PAINT_SIZE_LEVEL);
setThemeColor(PenConfig.THEME_COLOR);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, PenConfig.THEME_COLOR);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, PenConfig.THEME_COLOR);
BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? PenConfig.THEME_COLOR : Color.LTGRAY);
// mSettingView.setOutBorderColor(PenConfig.THEME_COLOR);
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_hand, PenConfig.THEME_COLOR);
}
/**
* 获取画布默认宽度
*
*/
private int getResizeWidth() {
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels) {
return (int) (dm.heightPixels * widthRate);
}
return (int) (dm.widthPixels * widthRate);
}
/**
* 获取画布默认高度
*
*/
private int getResizeHeight() {
int toolBarHeight = getResources().getDimensionPixelSize(R.dimen.sign_grid_toolbar_height);
int actionbarHeight = getResources().getDimensionPixelSize(R.dimen.sign_actionbar_height);
int statusBarHeight = StatusBarCompat.getStatusBarHeight(this);
int otherHeight = toolBarHeight + actionbarHeight + statusBarHeight;
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels) {
return (int) ((dm.widthPixels - otherHeight) * heightRate);
}
return (int) ((dm.heightPixels - otherHeight) * heightRate);
}
@Override
protected void initData() {
isCrop = getIntent().getBooleanExtra("crop", false);
format = getIntent().getStringExtra("format");
bgColor = getIntent().getIntExtra("background", Color.TRANSPARENT);
String mInitPath = getIntent().getStringExtra("image");
float bitmapWidth = getIntent().getFloatExtra("width", 1.0f);
float bitmapHeight = getIntent().getFloatExtra("height", 1.0f);
if (bitmapWidth > 0 && bitmapWidth <= 1.0f) {
widthRate = bitmapWidth;
mWidth = getResizeWidth();
} else {
hasSize = true;
mWidth = bitmapWidth;
}
if (bitmapHeight > 0 && bitmapHeight <= 1.0f) {
heightRate = bitmapHeight;
mHeight = getResizeHeight();
} else {
hasSize = true;
mHeight = bitmapHeight;
}
if (mWidth > CANVAS_MAX_WIDTH) {
Toast.makeText(getApplicationContext(), "画板宽度已超过" + CANVAS_MAX_WIDTH, Toast.LENGTH_LONG).show();
finish();
return;
}
if (mHeight > CANVAS_MAX_WIDTH) {
Toast.makeText(getApplicationContext(), "画板高度已超过" + CANVAS_MAX_WIDTH, Toast.LENGTH_LONG).show();
finish();
return;
}
//初始画板设置
if (!hasSize && !TextUtils.isEmpty(mInitPath)) {
Bitmap bitmap = BitmapFactory.decodeFile(mInitPath);
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
hasSize = true;
if (mWidth > CANVAS_MAX_WIDTH || mHeight > CANVAS_MAX_HEIGHT) {
bitmap = BitmapUtil.zoomImg(bitmap, CANVAS_MAX_WIDTH, CANVAS_MAX_WIDTH);
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
}
}
mPaintView.init((int) mWidth, (int) mHeight, mInitPath);
if (bgColor != Color.TRANSPARENT) {
mPaintView.setBackgroundColor(bgColor);
}
}
/**
* 横竖屏切换
*/
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (settingWindow != null) {
settingWindow.dismiss();
}
int resizeWidth = getResizeWidth();
int resizeHeight = getResizeHeight();
if (mPaintView != null && !hasSize) {
mPaintView.resize(mPaintView.getLastBitmap(), resizeWidth, resizeHeight);
}
}
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.btn_setting) {
showPaintSettingWindow();
} else if (i == R.id.btn_hand) {
//切换是否允许写字
mPaintView.setFingerEnable(!mPaintView.isFingerEnable());
if (mPaintView.isFingerEnable()) {
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_hand, PenConfig.THEME_COLOR);
} else {
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_drag, PenConfig.THEME_COLOR);
}
} else if (i == R.id.btn_clear) {
mPaintView.reset();
} else if (i == R.id.btn_undo) {
mPaintView.undo();
} else if (i == R.id.btn_redo) {
mPaintView.redo();
} else if (i == R.id.btn_pen) {
if (!mPaintView.isEraser()) {
mPaintView.setPenType(PaintView.TYPE_ERASER);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_eraser, PenConfig.THEME_COLOR);
} else {
mPaintView.setPenType(PaintView.TYPE_PEN);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, PenConfig.THEME_COLOR);
}
} else if (i == R.id.tv_ok) {
save();
} else if (i == R.id.tv_cancel) {
if (!mPaintView.isEmpty()) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
}
@Override
protected void onDestroy() {
if (mPaintView != null) {
mPaintView.release();
}
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
super.onDestroy();
}
/**
* 弹出画笔设置
*/
private void showPaintSettingWindow() {
settingWindow = new PaintSettingWindow(this);
settingWindow.setSettingListener(new PaintSettingWindow.OnSettingListener() {
@Override
public void onColorSetting(int color) {
mPaintView.setPaintColor(color);
mSettingView.setPaintColor(color);
}
@Override
public void onSizeSetting(int index) {
mSettingView.setRadiusLevel(index);
mPaintView.setPaintWidth(PaintSettingWindow.PEN_SIZES[index]);
}
});
View contentView = settingWindow.getContentView();
//需要先测量PopupWindow还未弹出时宽高为0
contentView.measure(SystemUtil.makeDropDownMeasureSpec(settingWindow.getWidth()),
SystemUtil.makeDropDownMeasureSpec(settingWindow.getHeight()));
int padding = DisplayUtil.dip2px(this, 45);
settingWindow.popAtTopRight();
settingWindow.showAsDropDown(mSettingView, -250, -2 * padding - settingWindow.getContentView().getMeasuredHeight());
}
private void initSaveProgressDlg() {
mSaveProgressDlg = new ProgressDialog(this);
mSaveProgressDlg.setMessage("正在保存,请稍候...");
mSaveProgressDlg.setCancelable(false);
}
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAVE_FAILED:
mSaveProgressDlg.dismiss();
Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_SHORT).show();
break;
case MSG_SAVE_SUCCESS:
mSaveProgressDlg.dismiss();
Intent intent = new Intent();
intent.putExtra(PenConfig.SAVE_PATH, mSavePath);
setResult(RESULT_OK, intent);
finish();
break;
default:
break;
}
}
};
/**
* 保存
*/
private void save() {
if (mPaintView.isEmpty()) {
Toast.makeText(getApplicationContext(), "没有写入任何文字", Toast.LENGTH_SHORT).show();
return;
}
//先检查是否有存储权限
// if (ContextCompat.checkSelfPermission(this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// Toast.makeText(getApplicationContext(), "没有读写存储的权限", Toast.LENGTH_SHORT).show();
// return;
// }
if (mSaveProgressDlg == null) {
initSaveProgressDlg();
}
mSaveProgressDlg.show();
new Thread(() -> {
try {
Bitmap result = mPaintView.buildAreaBitmap(isCrop);
if (PenConfig.FORMAT_JPG.equals(format) && bgColor == Color.TRANSPARENT) {
bgColor = Color.WHITE;
}
if (bgColor != Color.TRANSPARENT) {
result = BitmapUtil.drawBgToBitmap(result, bgColor);
}
if (result == null) {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
return;
}
mSavePath = BitmapUtil.saveImage(PaintActivity.this, result, 100, format);
if (mSavePath != null) {
mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget();
} else {
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
}
} catch (Exception e) {
}
}).start();
}
/**
* 画布有操作
*/
@Override
public void onOperateStatusChanged() {
mUndoView.setEnabled(mPaintView.canUndo());
mRedoView.setEnabled(mPaintView.canRedo());
mClearView.setEnabled(!mPaintView.isEmpty());
BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? PenConfig.THEME_COLOR : Color.LTGRAY);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? PenConfig.THEME_COLOR : Color.LTGRAY);
}
@Override
public void onBackPressed() {
if (!mPaintView.isEmpty()) {
showQuitTip();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
/**
* 弹出退出提示
*/
private void showQuitTip() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("当前文字未保存,是否退出?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) -> {
setResult(RESULT_CANCELED);
finish();
});
builder.show();
}
}

View File

@ -0,0 +1,38 @@
package com.za.signature.config;
/***
* 每个点的控制,关心三个因素:笔的宽度,坐标,透明数值
*
* @since 2018/06/15
* @author king
*/
public class ControllerPoint {
public float x;
public float y;
public float width;
public int alpha = 255;
public ControllerPoint() {
}
public ControllerPoint(float x, float y) {
this.x = x;
this.y = y;
}
public void set(float x, float y, float w) {
this.x = x;
this.y = y;
this.width = w;
}
public void set(ControllerPoint point) {
this.x = point.x;
this.y = point.y;
this.width = point.width;
}
}

View File

@ -0,0 +1,30 @@
package com.za.signature.config;
/***
* 触摸点信息
*
* @since 2018/06/15
* @author king
*/
public class MotionElement {
public float x;
public float y;
/**
* 压力值 物理设备决定的,和设计的设备有关系
*/
public float pressure;
/**
* 绘制的工具是否是手指或者是笔(触摸笔)
*/
public int toolType;
public MotionElement(float mx, float my, float mp, int ttype) {
x = mx;
y = my;
pressure = mp;
toolType = ttype;
}
}

View File

@ -0,0 +1,103 @@
package com.za.signature.config;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import com.za.signature.view.PaintSettingWindow;
/**
* 画笔配置
*
* @author king
* @since 2018/06/15
*/
public class PenConfig {
/**
* 画笔大小等级
*/
public static int PAINT_SIZE_LEVEL = 2;
/**
* 画笔颜色
*/
public static int PAINT_COLOR = Color.parseColor(PaintSettingWindow.PEN_COLORS[0]);
/**
* 笔锋控制值,越小笔锋越粗,越不明显
*/
public static final float DIS_VEL_CAL_FACTOR = 0.008f;
/**
* 主题颜色
*/
public static int THEME_COLOR = Color.parseColor("#0c53ab");
public static final String SAVE_PATH = "path";
private static final String SP_SETTING = "sp_sign_setting";
/**
* jpg格式
*/
public static final String FORMAT_JPG = "JPG";
/**
* png格式
*/
public static final String FORMAT_PNG = "PNG";
/**
* 保存画笔颜色设置
*/
public static void setPaintColor(Context context, int color) {
SharedPreferences sp = context.getSharedPreferences(SP_SETTING, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putInt("color", color);
editor.apply();
}
/**
* 获取画笔颜色
*/
public static int getPaintColor(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_SETTING, Context.MODE_PRIVATE);
return sp.getInt("color", PAINT_COLOR);
}
/**
* 保存画笔大小level
*/
public static void savePaintTextLevel(Context context, int size) {
SharedPreferences sp = context.getSharedPreferences(SP_SETTING, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putInt("sizeLevel", size);
editor.apply();
}
/**
* 从sp文件中获取选中画笔大小level
*/
public static int getPaintTextLevel(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_SETTING, Context.MODE_PRIVATE);
return sp.getInt("sizeLevel", PAINT_SIZE_LEVEL);
}
/**
* 获取是否第一次打开
*/
public static boolean getFirst(Context context) {
SharedPreferences sp = context.getSharedPreferences(SP_SETTING, Context.MODE_PRIVATE);
return sp.getBoolean("isFirst", true);
}
/**
* 设置是否第一次打开
*/
public static void setFirst(Context context, boolean isFirst) {
SharedPreferences sp = context.getSharedPreferences(SP_SETTING, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean("isFirst", isFirst);
editor.apply();
}
}

View File

@ -0,0 +1,240 @@
package com.za.signature.pen;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
import com.za.signature.config.ControllerPoint;
import com.za.signature.config.MotionElement;
import com.za.signature.config.PenConfig;
import com.za.signature.util.Bezier;
import java.util.ArrayList;
/**
* 画笔基类
*
* @author king
* @since 2018/06/15
*/
public abstract class BasePen {
/**
* 绘制计算的次数,数值越小计算的次数越多
*/
public static final int STEP_FACTOR = 20;
protected ArrayList<ControllerPoint> mHWPointList = new ArrayList<>();
protected ControllerPoint mLastPoint = new ControllerPoint(0, 0);
protected Paint mPaint;
/**
* 笔的宽度信息
*/
private double mBaseWidth;
private double mLastVel;
private double mLastWidth;
protected Bezier mBezier = new Bezier();
protected ControllerPoint mCurPoint;
public void setPaint(Paint paint) {
mPaint = paint;
mBaseWidth = paint.getStrokeWidth();
}
public void draw(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
//点的集合少 不去绘制
if (mHWPointList == null || mHWPointList.isEmpty()) {
return;
}
mCurPoint = mHWPointList.get(0);
doPreDraw(canvas);
}
private int lastId = 0;//记录最先/最后的手指id
public void onTouchEvent(MotionEvent event, Canvas canvas) {
// event会被下一次事件重用这里必须生成新的否则会有问题
int action = event.getAction() & event.getActionMasked();
MotionEvent event2 = MotionEvent.obtain(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
lastId = event2.getPointerId(0);
onDown(createMotionElement(event2), canvas);
return;
case MotionEvent.ACTION_POINTER_DOWN:
lastId = 0;
mLastVel = 0;
mLastPoint = new ControllerPoint(event2.getX(event2.getActionIndex()), event2.getY(event2.getActionIndex()));
break;
case MotionEvent.ACTION_MOVE:
if (lastId != event2.getPointerId(event2.getActionIndex())) {
return;
}
onMove(createMotionElement(event2), canvas);
return;
case MotionEvent.ACTION_POINTER_UP:
onUp(createMotionElement(event2), canvas);
return;
case MotionEvent.ACTION_UP:
lastId = event2.getPointerId(0);
onUp(createMotionElement(event2), canvas);
return;
default:
break;
}
}
/**
* 按下的事件
*/
public void onDown(MotionElement mElement, Canvas canvas) {
if (mPaint == null) {
throw new NullPointerException("paint不能为null");
}
if (getNewPaint(mPaint) != null) {
Paint paint = getNewPaint(mPaint);
mPaint = paint;
paint = null;
}
mHWPointList.clear();
//记录down的控制点的信息
ControllerPoint curPoint = new ControllerPoint(mElement.x, mElement.y);
mLastWidth = 0.7 * mBaseWidth;
//down下的点的宽度
curPoint.width = (float) mLastWidth;
mLastVel = 0;
//记录当前的点
mLastPoint = curPoint;
}
protected Paint getNewPaint(Paint paint) {
return null;
}
/**
* 手指移动的事件
*/
public void onMove(MotionElement mElement, Canvas canvas) {
ControllerPoint curPoint = new ControllerPoint(mElement.x, mElement.y);
double deltaX = curPoint.x - mLastPoint.x;
double deltaY = curPoint.y - mLastPoint.y;
//deltaX和deltay平方和的二次方根 想象一个例子 1+1的平方根为1.4 x²+y²开根号
//同理当滑动的越快的话deltaX+deltaY的值越大这个越大的话curDis也越大
double curDis = Math.hypot(deltaX, deltaY);
//我们求出的这个值越小,画的点或者是绘制椭圆形越多,这个值越大的话,绘制的越少,笔就越细,宽度越小
double curVel = curDis * PenConfig.DIS_VEL_CAL_FACTOR;
double curWidth;
//点的集合少,我们得必须改变宽度,每次点击的down的时候这个事件
if (mHWPointList.size() < 2) {
curWidth = calcNewWidth(curVel, mLastVel, curDis, 1.7,
mLastWidth);
curPoint.width = (float) curWidth;
mBezier.init(mLastPoint, curPoint);
} else {
mLastVel = curVel;
curWidth = calcNewWidth(curVel, mLastVel, curDis, 1.7,
mLastWidth);
curPoint.width = (float) curWidth;
mBezier.addNode(curPoint);
}
//每次移动的话,这里赋值新的值
mLastWidth = curWidth;
doMove(curDis);
mLastPoint = curPoint;
}
/**
* 手指抬起来的事件
*/
public void onUp(MotionElement mElement, Canvas canvas) {
if (mHWPointList.isEmpty()) {
return;
}
mCurPoint = new ControllerPoint(mElement.x, mElement.y);
double deltaX = mCurPoint.x - mLastPoint.x;
double deltaY = mCurPoint.y - mLastPoint.y;
double curDis = Math.hypot(deltaX, deltaY);
mCurPoint.width = 0;
mBezier.addNode(mCurPoint);
int steps = 1 + (int) curDis / STEP_FACTOR;
double step = 1.0 / steps;
for (double t = 0; t < 1.0; t += step) {
ControllerPoint point = mBezier.getPoint(t);
mHWPointList.add(point);
}
mBezier.end();
for (double t = 0; t < 1.0; t += step) {
ControllerPoint point = mBezier.getPoint(t);
mHWPointList.add(point);
}
draw(canvas);
clear();
}
/**
* 计算新的宽度信息
*/
public double calcNewWidth(double curVel, double lastVel, double curDis,
double factor, double lastWidth) {
double calVel = curVel * 0.6 + lastVel * (1 - 0.6);
double vfac = Math.log(factor * 2.0f) * (-calVel);
return mBaseWidth * Math.exp(vfac);
}
/**
* 创建触摸点信息
*/
public MotionElement createMotionElement(MotionEvent motionEvent) {
return new MotionElement(motionEvent.getX(0), motionEvent.getY(0),
motionEvent.getPressure(), motionEvent.getToolType(0));
}
/**
* 清除缓存的触摸点
*/
public void clear() {
mHWPointList.clear();
}
/**
* 绘制
* 当现在的点和触摸点的位置在一起的时候不用去绘制
*/
protected void drawToPoint(Canvas canvas, ControllerPoint point, Paint paint) {
if ((mCurPoint.x == point.x) && (mCurPoint.y == point.y)) {
return;
}
doDraw(canvas, point, paint);
}
/**
* 判断笔是否为空
*/
public boolean isNullPaint() {
return mPaint == null;
}
/**
* 移动的时候的处理方法
*/
protected abstract void doMove(double f);
/**
* 绘制方法
*/
protected abstract void doDraw(Canvas canvas, ControllerPoint point, Paint paint);
/**
* onDraw之前的操作
*/
protected abstract void doPreDraw(Canvas canvas);
}

View File

@ -0,0 +1,77 @@
package com.za.signature.pen;
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.view.MotionEvent;
/***
* 名称Eraser<br>
* 描述:橡皮擦
* 最近修改时间:
* @since 2018/5/7
* @author king
*/
public class Eraser {
private final Paint eraserPaint;
/**
* 画笔路径
*/
private Path mPath;
/**
* 记录上一个点的坐标
*/
private float mLastX;
private float mLastY;
public Eraser(int paintWidth) {
eraserPaint = new Paint();
eraserPaint.setStyle(Paint.Style.STROKE);
eraserPaint.setStrokeJoin(Paint.Join.ROUND);
eraserPaint.setStrokeCap(Paint.Cap.ROUND);
eraserPaint.setStrokeWidth(paintWidth);
eraserPaint.setFilterBitmap(true);
eraserPaint.setColor(Color.WHITE);
eraserPaint.setDither(true);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setAntiAlias(true);
mPath = new Path();
}
// public void setPaintWidth(int width) {
// eraserPaint.setStrokeWidth(width);
// }
public void handleEraserEvent(MotionEvent event, Canvas canvas) {
final float x = event.getX();
final float y = event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
mPath.reset();
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
mPath.quadTo(mLastX, mLastY, (x + mLastX) / 2, (y + mLastY) / 2);
canvas.drawPath(mPath, eraserPaint);
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP:
mPath.lineTo(mLastX, mLastY);
canvas.drawPath(mPath, eraserPaint);
break;
}
}
}

View File

@ -0,0 +1,79 @@
package com.za.signature.pen;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import com.za.signature.config.ControllerPoint;
/**
* 钢笔
*
* @since 2018/06/15
* @author king
*/
public class SteelPen extends BasePen {
@Override
protected void doPreDraw(Canvas canvas) {
for (int i = 1; i < mHWPointList.size(); i++) {
ControllerPoint point = mHWPointList.get(i);
drawToPoint(canvas, point, mPaint);
mCurPoint = point;
}
}
@Override
protected void doMove(double curDis) {
int steps = 1 + (int) curDis / STEP_FACTOR;
double step = 1.0 / steps;
for (double t = 0; t < 1.0; t += step) {
ControllerPoint point = mBezier.getPoint(t);
mHWPointList.add(point);
}
}
@Override
protected void doDraw(Canvas canvas, ControllerPoint point, Paint paint) {
drawLine(canvas, mCurPoint.x, mCurPoint.y, mCurPoint.width, point.x,
point.y, point.width, paint);
}
/**
* 绘制方法,实现笔锋效果
*/
private void drawLine(Canvas canvas, double x0, double y0, double w0, double x1, double y1, double w1, Paint paint) {
//求两个数字的平方根 x的平方+y的平方在开方记得X的平方+y的平方=1这就是一个园
double curDis = Math.hypot(x0 - x1, y0 - y1);
int steps;
//绘制的笔的宽度是多少,绘制多少个椭圆
if (paint.getStrokeWidth() < 6) {
steps = 1 + (int) (curDis / 2);
} else if (paint.getStrokeWidth() > 60) {
steps = 1 + (int) (curDis / 4);
} else {
steps = 1 + (int) (curDis / 3);
}
double deltaX = (x1 - x0) / steps;
double deltaY = (y1 - y0) / steps;
double deltaW = (w1 - w0) / steps;
double x = x0;
double y = y0;
double w = w0;
for (int i = 0; i < steps; i++) {
RectF oval = new RectF();
float top = (float) (y - w / 2.0f);
float left = (float) (x - w / 4.0f);
float right = (float) (x + w / 4.0f);
float bottom = (float) (y + w / 2.0f);
oval.set(left, top, right, bottom);
//最基本的实现,通过点控制线,绘制椭圆
canvas.drawOval(oval, paint);
x += deltaX;
y += deltaY;
w += deltaW;
}
}
}

View File

@ -0,0 +1,135 @@
package com.za.signature.util;
import com.za.signature.config.ControllerPoint;
/**
* 贝塞尔操作工具类对点的位置和宽度控制的bezier曲线主要是两个点都包含了宽度和点的坐标
*
* @since 2018/06/15
* @author king
*/
public class Bezier {
/**
* 控制点
*/
private final ControllerPoint mControl = new ControllerPoint();
/**
* 距离
*/
private final ControllerPoint mDestination = new ControllerPoint();
/**
* 下一个需要控制点
*/
private final ControllerPoint mNextControl = new ControllerPoint();
/**
* 资源的点
*/
private final ControllerPoint mSource = new ControllerPoint();
/**
* 初始化两个点,
*
* @param last 最后的点的信息
* @param cur 当前点的信息,当前点的信息,当前点的是根据事件获得,同时这个当前点的宽度是经过计算的得出的
*/
public void init(ControllerPoint last, ControllerPoint cur) {
init(last.x, last.y, last.width, cur.x, cur.y, cur.width);
}
public void init(float lastx, float lasty, float lastWidth, float x, float y, float width) {
//资源点设置,最后的点的为资源点
mSource.set(lastx, lasty, lastWidth);
float xmid = getMid(lastx, x);
float ymid = getMid(lasty, y);
float wmid = getMid(lastWidth, width);
//距离点为平均点
mDestination.set(xmid, ymid, wmid);
//控制点为当前的距离点
mControl.set(getMid(lastx, xmid), getMid(lasty, ymid), getMid(lastWidth, wmid));
//下个控制点为当前点
mNextControl.set(x, y, width);
}
public void addNode(ControllerPoint cur) {
addNode(cur.x, cur.y, cur.width);
}
/**
* 替换就的点,原来的距离点变换为资源点,控制点变为原来的下一个控制点,距离点取原来控制点的和新的的一半
* 下个控制点为新的点
*
* @param x 新的点的坐标
* @param y 新的点的坐标
* @param width
*/
public void addNode(float x, float y, float width) {
mSource.set(mDestination);
mControl.set(mNextControl);
mDestination.set(getMid(mNextControl.x, x), getMid(mNextControl.y, y), getMid(mNextControl.width, width));
mNextControl.set(x, y, width);
}
/**
* 结合手指抬起来的动作告诉现在的曲线控制点也必须变化其实在这里也不需要结合着up事件使用
* 因为在down的事件中所有点都会被重置然后设置这个没有多少意义但是可以改变下个事件的朝向改变
* 先留着,因为后面如果需要控制整个颜色的改变的话,我的依靠这个方法,还有按压的时间的变化
*/
public void end() {
mSource.set(mDestination);
float x = getMid(mNextControl.x, mSource.x);
float y = getMid(mNextControl.y, mSource.y);
float w = getMid(mNextControl.width, mSource.width);
mControl.set(x, y, w);
mDestination.set(mNextControl);
}
/**
* 获取点信息
*/
public ControllerPoint getPoint(double t) {
float x = (float) getX(t);
float y = (float) getY(t);
float w = (float) getW(t);
ControllerPoint point = new ControllerPoint();
point.set(x, y, w);
return point;
}
/**
* 三阶曲线的控制点
*/
private double getValue(double p0, double p1, double p2, double t) {
double a = p2 - 2 * p1 + p0;
double b = 2 * (p1 - p0);
double c = p0;
return a * t * t + b * t + c;
}
private double getX(double t) {
return getValue(mSource.x, mControl.x, mDestination.x, t);
}
private double getY(double t) {
return getValue(mSource.y, mControl.y, mDestination.y, t);
}
private double getW(double t) {
return getWidth(mSource.width, mDestination.width, t);
}
/**
* 获取两个数平均值
*
* @param x1 一个点的x
* @param x2 一个点的x
*/
private float getMid(float x1, float x2) {
return (float) ((x1 + x2) / 2.0);
}
private double getWidth(double w0, double w1, double t) {
return w0 + (w1 - w0) * t;
}
}

View File

@ -0,0 +1,394 @@
package com.za.signature.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.widget.ImageView;
import com.za.signature.config.PenConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 图像操作工具类
*
* @author king
* @since 2018/07/05
*/
public class BitmapUtil {
/**
* 逐行扫描 清除边界空白
*
* @param blank 边距留多少个像素
* @param color 背景色限定
* @return 清除边界后的Bitmap
*/
public static Bitmap clearBlank(Bitmap mBitmap, int blank, int color) {
if (mBitmap != null) {
int height = mBitmap.getHeight();
int width = mBitmap.getWidth();
int top = 0, left = 0, right = 0, bottom = 0;
int[] widthPixels = new int[width];
boolean isStop;
for (int y = 0; y < height; y++) {
mBitmap.getPixels(widthPixels, 0, width, 0, y, width, 1);
isStop = false;
for (int pix : widthPixels) {
if (pix != color) {
top = y;
isStop = true;
break;
}
}
if (isStop) {
break;
}
}
for (int y = height - 1; y >= 0; y--) {
mBitmap.getPixels(widthPixels, 0, width, 0, y, width, 1);
isStop = false;
for (int pix : widthPixels) {
if (pix != color) {
bottom = y;
isStop = true;
break;
}
}
if (isStop) {
break;
}
}
widthPixels = new int[height];
for (int x = 0; x < width; x++) {
mBitmap.getPixels(widthPixels, 0, 1, x, 0, 1, height);
isStop = false;
for (int pix : widthPixels) {
if (pix != color) {
left = x;
isStop = true;
break;
}
}
if (isStop) {
break;
}
}
for (int x = width - 1; x > 0; x--) {
mBitmap.getPixels(widthPixels, 0, 1, x, 0, 1, height);
isStop = false;
for (int pix : widthPixels) {
if (pix != color) {
right = x;
isStop = true;
break;
}
}
if (isStop) {
break;
}
}
if (blank < 0) {
blank = 0;
}
left = Math.max(left - blank, 0);
top = Math.max(top - blank, 0);
right = Math.min(right + blank, width - 1);
bottom = Math.min(bottom + blank, height - 1);
return Bitmap.createBitmap(mBitmap, left, top, right - left, bottom - top);
} else {
return null;
}
}
/**
* 清除bitmap左右边界空白
*
* @param mBitmap 源图
* @param blank 边距留多少个像素
* @param color 背景色限定
* @return 清除后的bitmap
*/
public static Bitmap clearLRBlank(Bitmap mBitmap, int blank, int color) {
if (mBitmap != null) {
int height = mBitmap.getHeight();
int width = mBitmap.getWidth();
int left = 0, right = 0;
int[] pixs = new int[height];
boolean isStop;
for (int x = 0; x < width; x++) {
mBitmap.getPixels(pixs, 0, 1, x, 0, 1, height);
isStop = false;
for (int pix : pixs) {
if (pix != color) {
left = x;
isStop = true;
break;
}
}
if (isStop) {
break;
}
}
for (int x = width - 1; x > 0; x--) {
mBitmap.getPixels(pixs, 0, 1, x, 0, 1, height);
isStop = false;
for (int pix : pixs) {
if (pix != color) {
right = x;
isStop = true;
break;
}
}
if (isStop) {
break;
}
}
if (blank < 0) {
blank = 0;
}
left = Math.max(left - blank, 0);
right = Math.min(right + blank, width - 1);
return Bitmap.createBitmap(mBitmap, left, 0, right - left, height);
} else {
return null;
}
}
/**
* 给Bitmap添加背景色
*
* @param srcBitmap 源图
* @param color 背景颜色
* @return 修改背景后的bitmap
*/
public static Bitmap drawBgToBitmap(Bitmap srcBitmap, int color) {
Paint paint = new Paint();
paint.setColor(color);
Bitmap bitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
Canvas canvas = new Canvas(bitmap);
canvas.drawRect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight(), paint);
canvas.drawBitmap(srcBitmap, 0, 0, paint);
return bitmap;
}
/**
* 保存图像到本地
*
* @param bmp 源图
* @param quality 压缩质量
* @param format 图片格式
* @return 保存后的图片地址
*/
public static String saveImage(Context context, Bitmap bmp, int quality, String format) {
if (bmp == null) {
return null;
}
FileOutputStream fos = null;
try {
String appDir = context.getExternalCacheDir().getAbsolutePath();
File saveDir = new File(appDir, "signImg");
if (!saveDir.exists()) {
saveDir.mkdirs();
}
String fileName;
Bitmap.CompressFormat compressFormat;
if (PenConfig.FORMAT_JPG.equals(format)) {
compressFormat = Bitmap.CompressFormat.JPEG;
fileName = System.currentTimeMillis() + ".jpg";
} else {
compressFormat = Bitmap.CompressFormat.PNG;
fileName = System.currentTimeMillis() + ".png";
}
File file = new File(saveDir, fileName);
fos = new FileOutputStream(file);
bmp.compress(compressFormat, quality, fos);
fos.flush();
return file.getAbsolutePath();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 根据宽度缩放图片,高度等比例
*
* @param bm 源图
* @param newWidth 新宽度
* @return 缩放后的bitmap
*/
public static Bitmap zoomImg(Bitmap bm, int newWidth) {
// 获得图片的宽高
int width = bm.getWidth();
int height = bm.getHeight();
// 计算缩放比例
float ratio = ((float) newWidth) / width;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(ratio, ratio);
// 得到新的图片
return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
}
/**
* 缩放图片至指定宽高
*
* @param bm 源图
* @param newWidth 新宽度
* @param newHeight 新高度
* @return 缩放后的bitmap
*/
public static Bitmap zoomImage(Bitmap bm, int newWidth, int newHeight) {
// 获得图片的宽高
int width = bm.getWidth();
int height = bm.getHeight();
// 计算缩放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的图片
return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
}
/**
* 根据宽高之中最大缩放比缩放图片
*
* @param bitmap 源图
* @param newWidth 新宽度
* @param newHeight 新高度
* @return 缩放后的bitmap
*/
public static Bitmap zoomImg(Bitmap bitmap, int newWidth,
int newHeight) {
// 获取这个图片的宽和高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
// 创建操作图片用的matrix对象
Matrix matrix = new Matrix();
// 计算宽高缩放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
float ratio = Math.max(scaleWidth, scaleHeight);
// 缩放图片动作
matrix.postScale(ratio, ratio);
return Bitmap.createBitmap(bitmap, 0, 0, width,
height, matrix, true);
}
/**
* 给图片右下角添加水印
*
* @param src 源图
* @param watermark 水印图
* @param bgColor 背景色
* @param fixed 源图是否固定大小,固定则在源图上绘制印章,不固定则动态改变图片大小
* @return 添加水印后的图片
*/
public static Bitmap addWaterMask(Bitmap src, Bitmap watermark, int bgColor, boolean fixed) {
int w = src.getWidth();
int h = src.getHeight();
//获取原始水印图片的宽、高
int w2 = watermark.getWidth();
int h2 = watermark.getHeight();
//合理控制水印大小
Matrix matrix1 = new Matrix();
float ratio;
ratio = (float) w2 / w;
if (ratio > 1.0f && ratio <= 2.0f) {
ratio = 0.7f;
} else if (ratio > 2.0f) {
ratio = 0.5f;
} else if (ratio <= 0.2f) {
ratio = 2.0f;
} else if (ratio < 0.3f) {
ratio = 1.5f;
} else if (ratio <= 0.4f) {
ratio = 1.2f;
} else if (ratio < 1.0f) {
ratio = 1.0f;
}
matrix1.postScale(ratio, ratio);
watermark = Bitmap.createBitmap(watermark, 0, 0, w2, h2, matrix1, true);
//获取新的水印图片的宽、高
w2 = watermark.getWidth();
h2 = watermark.getHeight();
if (!fixed) {
if (w < 1.5 * w2) {
w = w + w2;
}
if (h < 2 * h2) {
h = h + h2;
}
}
// 创建一个新的和SRC长度宽度一样的位图
Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
Canvas cv = new Canvas(result);
cv.drawColor(bgColor);
//在canvas上绘制原图和新的水印图
cv.drawBitmap(src, 0, 0, null);
//水印图绘制在画布的右下角距离右边和底部都为20
cv.drawBitmap(watermark, w - w2 - 20, h - h2 - 20, null);
cv.save();
cv.restore();
return result;
}
/**
* 修改图片颜色
*
* @param inBitmap 源图
* @param color 颜色
* @return 修改颜色后的图片
*/
public static Bitmap changeBitmapColor(Bitmap inBitmap, int color) {
if (inBitmap == null) {
return null;
}
Bitmap outBitmap = Bitmap.createBitmap(inBitmap.getWidth(), inBitmap.getHeight(), inBitmap.getConfig());
Canvas canvas = new Canvas(outBitmap);
Paint paint = new Paint();
paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(inBitmap, 0, 0, paint);
return outBitmap;
}
/**
* 设置ImageView的图片支持改变图片颜色
*
* @param iv
* @param id
* @param color
*/
public static void setImage(ImageView iv, int id, int color) {
Bitmap bitmap = BitmapFactory.decodeResource(iv.getResources(), id);
iv.setImageBitmap(BitmapUtil.changeBitmapColor(bitmap, color));
}
}

View File

@ -0,0 +1,71 @@
package com.za.signature.util;
import android.content.Context;
import android.view.Display;
import android.view.WindowManager;
/***
* 名称DisplayUtil<br>
* 描述:显示相关操作工具类
* 最近修改时间:
* @since 2017/11/16
* @author king
*/
public class DisplayUtil {
/**
* dp 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* px(像素) 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* px(像素) 转成为 dp
*/
public static int px2sp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / scale + 0.5f);
}
/**
* px(像素) 转成为 dp
*/
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
/**
* 获取屏幕宽度
*/
public static int getScreenWidth(Context context) {
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
return display.getWidth();
}
/**
* 获取屏幕高度
* @param context
* @return
*/
public static int getScreenHeight(Context context) {
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
return display.getHeight();
}
}

View File

@ -0,0 +1,36 @@
package com.za.signature.util;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
/***
* 沉浸式状态栏
*
* @since 2018-11-24
* @author king
*/
public class StatusBarCompat {
private static final int INVALID_VAL = -1;
private static final int COLOR_DEFAULT = Color.parseColor("#20000000");
public static void compat(Activity activity, int statusColor) {
if (statusColor != INVALID_VAL) {
activity.getWindow().setStatusBarColor(statusColor);
}
}
public static void compat(Activity activity) {
compat(activity, INVALID_VAL);
}
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}

View File

@ -0,0 +1,211 @@
package com.za.signature.util;
import android.graphics.Bitmap;
import java.util.ArrayList;
import java.util.List;
/**
* 撤销/恢复 操作工具类
*
* @author king
* @since 2018/06/27
*/
public class StepOperator {
/**
* 缓存步骤数
*/
private static final int CAPACITY = 12;
/**
* 保存每一步绘制的bitmap
*/
private List<Bitmap> mBitmaps = null;
/**
* 允许缓存Bitmap的最大宽度限制过大容易内存溢出
*/
public static int MAX_CACHE_BITMAP_WIDTH = 1024;
/**
* 当前位置
*/
private int currentIndex;
private boolean isUndo = false;
/**
* 最大缓存内存大小
*/
private final int cacheMemory;
public StepOperator() {
if (mBitmaps == null) {
mBitmaps = new ArrayList<>();
}
currentIndex = -1;
int maxMemory = (int) Runtime.getRuntime().maxMemory();
cacheMemory = maxMemory / 3;
}
/**
* 重置
*/
public void reset() {
if (mBitmaps != null) {
for (Bitmap bitmap : mBitmaps) {
bitmap.recycle();
}
mBitmaps.clear();
}
currentIndex = -1;
}
/**
* 内存是否足够
*
*/
private boolean isMemoryEnable() {
int bitmapCache = 0;
for (Bitmap bitmap : mBitmaps) {
bitmapCache += bitmap.getRowBytes() * bitmap.getHeight();
}
return bitmapCache <= cacheMemory;
}
/**
* 缓存绘制的Bitmap
*
* @param bitmap
*/
public void addBitmap(Bitmap bitmap) {
if (mBitmaps == null) {
return;
}
try {
if (!isMemoryEnable() && mBitmaps.size() > 1) {
mBitmaps.get(1).recycle();
//删除第一笔0的位置有空的占位图
mBitmaps.remove(1);
}
if (currentIndex != -1 && isUndo) {
for (int i = currentIndex + 1; i < mBitmaps.size(); i++) {
mBitmaps.get(i).recycle();
}
mBitmaps = mBitmaps.subList(0, currentIndex + 1);
isUndo = false;
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), null, true);
mBitmaps.add(bitmap);
currentIndex = mBitmaps.size() - 1;
if (mBitmaps.size() > CAPACITY) {
mBitmaps.get(1).recycle();
//删除第一笔0的位置有空的占位图
mBitmaps.remove(1);
}
} catch (Exception e) {
} catch (OutOfMemoryError e) {
}
}
/**
* 判断当前是否第一步
*
* @return
*/
public boolean currentIsFirst() {
if (mBitmaps != null && currentIndex == 0) {
return true;
}
return false;
}
/**
* 判断当前是否最后一步
*
*/
public boolean currentIsLast() {
return mBitmaps != null && currentIndex == mBitmaps.size() - 1;
}
/**
* 撤销
*/
public void undo(Bitmap srcBitmap) {
if (mBitmaps == null) {
return;
}
isUndo = true;
currentIndex--;
if (currentIndex < 0) {
currentIndex = 0;
}
try {
Bitmap bitmap = mBitmaps.get(currentIndex);
if (bitmap.isRecycled()) {
return;
}
bitmap = BitmapUtil.zoomImg(bitmap, srcBitmap.getWidth());
if (bitmap.getWidth() > srcBitmap.getWidth() || bitmap.getHeight() > srcBitmap.getHeight()) {
bitmap = BitmapUtil.zoomImage(bitmap, srcBitmap.getWidth(), srcBitmap.getHeight());
}
//保存所有的像素的数组,图片宽×高
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
srcBitmap.setPixels(pixels, 0, bitmap.getWidth(), 0, 0,
bitmap.getWidth(), bitmap.getHeight());
} catch (OutOfMemoryError e) {
}
}
/**
* 恢复
*/
public void redo(Bitmap srcBitmap) {
if (mBitmaps == null) {
return;
}
currentIndex++;
int lastIndex = mBitmaps.size() - 1;
if (currentIndex >= lastIndex) {
currentIndex = lastIndex;
}
try {
Bitmap bitmap = mBitmaps.get(currentIndex);
if (bitmap.isRecycled()) {
return;
}
bitmap = BitmapUtil.zoomImg(bitmap, srcBitmap.getWidth());
if (bitmap.getWidth() > srcBitmap.getWidth() || bitmap.getHeight() > srcBitmap.getHeight()) {
bitmap = BitmapUtil.zoomImage(bitmap, srcBitmap.getWidth(), srcBitmap.getHeight());
}
//保存所有的像素的数组,图片宽×高
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
srcBitmap.setPixels(pixels, 0, bitmap.getWidth(), 0, 0,
bitmap.getWidth(), bitmap.getHeight());
} catch (OutOfMemoryError e) {
}
}
/**
* 清空
*/
public void freeBitmaps() {
if (mBitmaps == null) {
return;
}
for (Bitmap bitmap : mBitmaps) {
bitmap.recycle();
}
mBitmaps.clear();
mBitmaps = null;
currentIndex = -1;
}
}

View File

@ -0,0 +1,527 @@
package com.za.signature.util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout.LayoutParams;
import java.lang.reflect.Method;
/***
* 沉浸式状态栏
*
* 最近修改时间2018年09月17日 17:48分
* @since 2018-09-17
* @author king
*/
public class SystemBarTintManager {
static {
// Android allows a system property to override the presence of the navigation bar.
// Used by the emulator.
// See https://github.com/android/platform_frameworks_base/blob/master/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java#L1076
try {
Class c = Class.forName("android.os.SystemProperties");
Method m = c.getDeclaredMethod("get", String.class);
m.setAccessible(true);
sNavBarOverride = (String) m.invoke(null, "qemu.hw.mainkeys");
} catch (Throwable e) {
sNavBarOverride = null;
}
}
/**
* The default system bar tint color value.
*/
public static final int DEFAULT_TINT_COLOR = 0x99000000;
private static String sNavBarOverride;
private final SystemBarConfig mConfig;
private boolean mStatusBarAvailable;
private boolean mNavBarAvailable;
private boolean mStatusBarTintEnabled;
private boolean mNavBarTintEnabled;
private View mStatusBarTintView;
private View mNavBarTintView;
/**
* Constructor. Call this in the host activity onCreate method after its
* content view has been set. You should always create new instances when
* the host activity is recreated.
*
* @param activity The host activity.
*/
public SystemBarTintManager(Activity activity) {
Window win = activity.getWindow();
ViewGroup decorViewGroup = (ViewGroup) win.getDecorView();
// check theme attrs
int[] attrs = {android.R.attr.windowTranslucentStatus,
android.R.attr.windowTranslucentNavigation};
TypedArray a = activity.obtainStyledAttributes(attrs);
try {
mStatusBarAvailable = a.getBoolean(0, false);
mNavBarAvailable = a.getBoolean(1, false);
} finally {
a.recycle();
}
// check window flags
WindowManager.LayoutParams winParams = win.getAttributes();
int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if ((winParams.flags & bits) != 0) {
mStatusBarAvailable = true;
}
bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
if ((winParams.flags & bits) != 0) {
mNavBarAvailable = true;
}
mConfig = new SystemBarConfig(activity, mStatusBarAvailable, mNavBarAvailable);
// device might not have virtual navigation keys
if (!mConfig.hasNavigtionBar()) {
mNavBarAvailable = false;
}
if (mStatusBarAvailable) {
setupStatusBarView(activity, decorViewGroup);
}
if (mNavBarAvailable) {
setupNavBarView(activity, decorViewGroup);
}
}
/**
* Enable tinting of the system status bar.
*
* If the platform is running Jelly Bean or earlier, or translucent system
* UI modes have not been enabled in either the theme or via window flags,
* then this method does nothing.
*
* @param enabled True to enable tinting, false to disable it (default).
*/
public void setStatusBarTintEnabled(boolean enabled) {
mStatusBarTintEnabled = enabled;
if (mStatusBarAvailable) {
mStatusBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
}
/**
* Enable tinting of the system navigation bar.
*
* If the platform does not have soft navigation keys, is running Jelly Bean
* or earlier, or translucent system UI modes have not been enabled in either
* the theme or via window flags, then this method does nothing.
*
* @param enabled True to enable tinting, false to disable it (default).
*/
public void setNavigationBarTintEnabled(boolean enabled) {
mNavBarTintEnabled = enabled;
if (mNavBarAvailable) {
mNavBarTintView.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
}
/**
* Apply the specified color tint to all system UI bars.
*
* @param color The color of the background tint.
*/
public void setTintColor(int color) {
setStatusBarTintColor(color);
setNavigationBarTintColor(color);
}
/**
* Apply the specified drawable or color resource to all system UI bars.
*
* @param res The identifier of the resource.
*/
public void setTintResource(int res) {
setStatusBarTintResource(res);
setNavigationBarTintResource(res);
}
/**
* Apply the specified drawable to all system UI bars.
*
* @param drawable The drawable to use as the background, or null to remove it.
*/
public void setTintDrawable(Drawable drawable) {
setStatusBarTintDrawable(drawable);
setNavigationBarTintDrawable(drawable);
}
/**
* Apply the specified alpha to all system UI bars.
*
* @param alpha The alpha to use
*/
public void setTintAlpha(float alpha) {
setStatusBarAlpha(alpha);
setNavigationBarAlpha(alpha);
}
/**
* Apply the specified color tint to the system status bar.
*
* @param color The color of the background tint.
*/
public void setStatusBarTintColor(int color) {
if (mStatusBarAvailable) {
mStatusBarTintView.setBackgroundColor(color);
}
}
/**
* Apply the specified drawable or color resource to the system status bar.
*
* @param res The identifier of the resource.
*/
public void setStatusBarTintResource(int res) {
if (mStatusBarAvailable) {
mStatusBarTintView.setBackgroundResource(res);
}
}
/**
* Apply the specified drawable to the system status bar.
*
* @param drawable The drawable to use as the background, or null to remove it.
*/
@SuppressWarnings("deprecation")
public void setStatusBarTintDrawable(Drawable drawable) {
if (mStatusBarAvailable) {
mStatusBarTintView.setBackgroundDrawable(drawable);
}
}
/**
* Apply the specified alpha to the system status bar.
*
* @param alpha The alpha to use
*/
public void setStatusBarAlpha(float alpha) {
if (mStatusBarAvailable) {
mStatusBarTintView.setAlpha(alpha);
}
}
/**
* Apply the specified color tint to the system navigation bar.
*
* @param color The color of the background tint.
*/
public void setNavigationBarTintColor(int color) {
if (mNavBarAvailable) {
mNavBarTintView.setBackgroundColor(color);
}
}
/**
* Apply the specified drawable or color resource to the system navigation bar.
*
* @param res The identifier of the resource.
*/
public void setNavigationBarTintResource(int res) {
if (mNavBarAvailable) {
mNavBarTintView.setBackgroundResource(res);
}
}
/**
* Apply the specified drawable to the system navigation bar.
*
* @param drawable The drawable to use as the background, or null to remove it.
*/
@SuppressWarnings("deprecation")
public void setNavigationBarTintDrawable(Drawable drawable) {
if (mNavBarAvailable) {
mNavBarTintView.setBackgroundDrawable(drawable);
}
}
/**
* Apply the specified alpha to the system navigation bar.
*
* @param alpha The alpha to use
*/
public void setNavigationBarAlpha(float alpha) {
if (mNavBarAvailable) {
mNavBarTintView.setAlpha(alpha);
}
}
/**
* Get the system bar configuration.
*
* @return The system bar configuration for the current device configuration.
*/
public SystemBarConfig getConfig() {
return mConfig;
}
/**
* Is tinting enabled for the system status bar?
*
* @return True if enabled, False otherwise.
*/
public boolean isStatusBarTintEnabled() {
return mStatusBarTintEnabled;
}
/**
* Is tinting enabled for the system navigation bar?
*
* @return True if enabled, False otherwise.
*/
public boolean isNavBarTintEnabled() {
return mNavBarTintEnabled;
}
private void setupStatusBarView(Context context, ViewGroup decorViewGroup) {
mStatusBarTintView = new View(context);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getStatusBarHeight());
params.gravity = Gravity.TOP;
if (mNavBarAvailable && !mConfig.isNavigationAtBottom()) {
params.rightMargin = mConfig.getNavigationBarWidth();
}
mStatusBarTintView.setLayoutParams(params);
mStatusBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
mStatusBarTintView.setVisibility(View.GONE);
decorViewGroup.addView(mStatusBarTintView);
}
private void setupNavBarView(Context context, ViewGroup decorViewGroup) {
mNavBarTintView = new View(context);
LayoutParams params;
if (mConfig.isNavigationAtBottom()) {
params = new LayoutParams(LayoutParams.MATCH_PARENT, mConfig.getNavigationBarHeight());
params.gravity = Gravity.BOTTOM;
} else {
params = new LayoutParams(mConfig.getNavigationBarWidth(), LayoutParams.MATCH_PARENT);
params.gravity = Gravity.RIGHT;
}
mNavBarTintView.setLayoutParams(params);
mNavBarTintView.setBackgroundColor(DEFAULT_TINT_COLOR);
mNavBarTintView.setVisibility(View.GONE);
decorViewGroup.addView(mNavBarTintView);
}
/**
* Class which describes system bar sizing and other characteristics for the current
* device configuration.
*
*/
public static class SystemBarConfig {
private static final String STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height";
private static final String NAV_BAR_HEIGHT_RES_NAME = "navigation_bar_height";
private static final String NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME = "navigation_bar_height_landscape";
private static final String NAV_BAR_WIDTH_RES_NAME = "navigation_bar_width";
private static final String SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar";
private final boolean mTranslucentStatusBar;
private final boolean mTranslucentNavBar;
private final int mStatusBarHeight;
private final int mActionBarHeight;
private final boolean mHasNavigationBar;
private final int mNavigationBarHeight;
private final int mNavigationBarWidth;
private final boolean mInPortrait;
private final float mSmallestWidthDp;
private SystemBarConfig(Activity activity, boolean translucentStatusBar, boolean traslucentNavBar) {
Resources res = activity.getResources();
mInPortrait = (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
mSmallestWidthDp = getSmallestWidthDp(activity);
mStatusBarHeight = getInternalDimensionSize(res, STATUS_BAR_HEIGHT_RES_NAME);
mActionBarHeight = getActionBarHeight(activity);
mNavigationBarHeight = getNavigationBarHeight(activity);
mNavigationBarWidth = getNavigationBarWidth(activity);
mHasNavigationBar = (mNavigationBarHeight > 0);
mTranslucentStatusBar = translucentStatusBar;
mTranslucentNavBar = traslucentNavBar;
}
private int getActionBarHeight(Context context) {
int result = 0;
TypedValue tv = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
result = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources().getDisplayMetrics());
return result;
}
private int getNavigationBarHeight(Context context) {
Resources res = context.getResources();
int result = 0;
if (hasNavBar(context)) {
String key;
if (mInPortrait) {
key = NAV_BAR_HEIGHT_RES_NAME;
} else {
key = NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME;
}
return getInternalDimensionSize(res, key);
}
return result;
}
private int getNavigationBarWidth(Context context) {
Resources res = context.getResources();
int result = 0;
if (hasNavBar(context)) {
return getInternalDimensionSize(res, NAV_BAR_WIDTH_RES_NAME);
}
return result;
}
private boolean hasNavBar(Context context) {
Resources res = context.getResources();
int resourceId = res.getIdentifier(SHOW_NAV_BAR_RES_NAME, "bool", "android");
if (resourceId != 0) {
boolean hasNav = res.getBoolean(resourceId);
// check override flag (see static block)
if ("1".equals(sNavBarOverride)) {
hasNav = false;
} else if ("0".equals(sNavBarOverride)) {
hasNav = true;
}
return hasNav;
} else { // fallback
return !ViewConfiguration.get(context).hasPermanentMenuKey();
}
}
private int getInternalDimensionSize(Resources res, String key) {
int result = 0;
int resourceId = res.getIdentifier(key, "dimen", "android");
if (resourceId > 0) {
result = res.getDimensionPixelSize(resourceId);
}
return result;
}
@SuppressLint("NewApi")
private float getSmallestWidthDp(Activity activity) {
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
float widthDp = metrics.widthPixels / metrics.density;
float heightDp = metrics.heightPixels / metrics.density;
return Math.min(widthDp, heightDp);
}
/**
* Should a navigation bar appear at the bottom of the screen in the current
* device configuration? A navigation bar may appear on the right side of
* the screen in certain configurations.
*
* @return True if navigation should appear at the bottom of the screen, False otherwise.
*/
public boolean isNavigationAtBottom() {
return (mSmallestWidthDp >= 600 || mInPortrait);
}
/**
* Get the height of the system status bar.
*
* @return The height of the status bar (in pixels).
*/
public int getStatusBarHeight() {
return mStatusBarHeight;
}
/**
* Get the height of the action bar.
*
* @return The height of the action bar (in pixels).
*/
public int getActionBarHeight() {
return mActionBarHeight;
}
/**
* Does this device have a system navigation bar?
*
* @return True if this device uses soft key navigation, False otherwise.
*/
public boolean hasNavigtionBar() {
return mHasNavigationBar;
}
/**
* Get the height of the system navigation bar.
*
* @return The height of the navigation bar (in pixels). If the device does not have
* soft navigation keys, this will always return 0.
*/
public int getNavigationBarHeight() {
return mNavigationBarHeight;
}
/**
* Get the width of the system navigation bar when it is placed vertically on the screen.
*
* @return The width of the navigation bar (in pixels). If the device does not have
* soft navigation keys, this will always return 0.
*/
public int getNavigationBarWidth() {
return mNavigationBarWidth;
}
/**
* Get the layout inset for any system UI that appears at the top of the screen.
*
* @param withActionBar True to include the height of the action bar, False otherwise.
* @return The layout inset (in pixels).
*/
public int getPixelInsetTop(boolean withActionBar) {
return (mTranslucentStatusBar ? mStatusBarHeight : 0) + (withActionBar ? mActionBarHeight : 0);
}
/**
* Get the layout inset for any system UI that appears at the bottom of the screen.
*
* @return The layout inset (in pixels).
*/
public int getPixelInsetBottom() {
if (mTranslucentNavBar && isNavigationAtBottom()) {
return mNavigationBarHeight;
} else {
return 0;
}
}
/**
* Get the layout inset for any system UI that appears at the right of the screen.
*
* @return The layout inset (in pixels).
*/
public int getPixelInsetRight() {
if (mTranslucentNavBar && !isNavigationAtBottom()) {
return mNavigationBarWidth;
} else {
return 0;
}
}
}
}

View File

@ -0,0 +1,129 @@
package com.za.signature.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/***
* 显示相关操作工具类
*
* @since 2017/11/16
* @author king
*/
public class SystemUtil {
/**
* 判断是否平板设备
* @param context
* @return true:平板,false:手机
*/
public static boolean isTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >=
Configuration.SCREENLAYOUT_SIZE_LARGE;
}
/**
* 测量View的高度
*/
@SuppressWarnings("ResourceType")
public static int makeDropDownMeasureSpec(int measureSpec) {
int mode;
if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {
mode = View.MeasureSpec.UNSPECIFIED;
} else {
mode = View.MeasureSpec.EXACTLY;
}
return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);
}
/**
* 禁止软键盘弹出
*/
public static void disableShowInput(Context context, EditText editText) {
Class<EditText> cls = EditText.class;
Method method;
try {
method = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
method.setAccessible(true);
method.invoke(editText, false);
} catch (Exception e) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
}
}
/**
* 禁止输入框复制粘贴菜单
*/
@SuppressLint("ClickableViewAccessibility")
public static void disableCopyAndPaste(final EditText editText) {
try {
if (editText == null) {
return;
}
editText.setOnLongClickListener(v -> true);
editText.setLongClickable(false);
editText.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
setInsertionDisabled(editText);
}
return false;
});
editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private static void setInsertionDisabled(EditText editText) {
try {
Field editorField = TextView.class.getDeclaredField("mEditor");
editorField.setAccessible(true);
Object editorObject = editorField.get(editText);
Class editorClass = Class.forName("android.widget.Editor");
Field mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled");
mInsertionControllerEnabledField.setAccessible(true);
mInsertionControllerEnabledField.set(editorObject, false);
Field mSelectionControllerEnabledField = editorClass.getDeclaredField("mSelectionControllerEnabled");
mSelectionControllerEnabledField.setAccessible(true);
mSelectionControllerEnabledField.set(editorObject, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,481 @@
package com.za.signature.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatImageView;
import com.za.servicing.R;
import com.za.signature.util.BitmapUtil;
public class CircleImageView extends AppCompatImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_4444;
private static final int COLORDRAWABLE_DIMENSION = 2;
private static final int DEFAULT_BORDER_WIDTH = 0;
private static final int DEFAULT_BORDER_COLOR = Color.parseColor("#0c53ab");
private static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.WHITE;
private static final boolean DEFAULT_BORDER_OVERLAY = false;
private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();
private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
private final Paint mBorderPaint = new Paint();
private final Paint mCircleBackgroundPaint = new Paint();
private int mBorderColor = DEFAULT_BORDER_COLOR;
private int mBorderWidth = DEFAULT_BORDER_WIDTH;
private int mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR;
private Bitmap mBitmap;
private BitmapShader mBitmapShader;
private int mBitmapWidth;
private int mBitmapHeight;
private float mDrawableRadius;
private float mBorderRadius;
private ColorFilter mColorFilter;
private boolean mReady;
private boolean mSetupPending;
private boolean mBorderOverlay;
private boolean mDisableCircularTransformation;
public CircleImageView(Context context) {
super(context);
init();
}
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
// Look for deprecated civ_fill_color if civ_circle_background_color is not set
if (a.hasValue(R.styleable.CircleImageView_civ_circle_background_color)) {
mCircleBackgroundColor = a.getColor(R.styleable.CircleImageView_civ_circle_background_color,
DEFAULT_CIRCLE_BACKGROUND_COLOR);
} else if (a.hasValue(R.styleable.CircleImageView_civ_fill_color)) {
mCircleBackgroundColor = a.getColor(R.styleable.CircleImageView_civ_fill_color,
DEFAULT_CIRCLE_BACKGROUND_COLOR);
}
a.recycle();
init();
}
private void init() {
super.setScaleType(SCALE_TYPE);
mReady = true;
setOutlineProvider(new OutlineProvider());
if (mSetupPending) {
setup();
mSetupPending = false;
}
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
}
}
@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (adjustViewBounds) {
throw new IllegalArgumentException("adjustViewBounds not supported.");
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mDisableCircularTransformation) {
super.onDraw(canvas);
return;
}
if (mBitmap == null) {
return;
}
if (mCircleBackgroundColor != Color.TRANSPARENT) {
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint);
}
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
if (mBorderWidth > 0) {
canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setup();
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
setup();
}
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
super.setPaddingRelative(start, top, end, bottom);
setup();
}
public int getBorderColor() {
return mBorderColor;
}
public void setBorderColor(@ColorInt int borderColor) {
if (borderColor == mBorderColor) {
return;
}
mBorderColor = borderColor;
mBorderPaint.setColor(mBorderColor);
invalidate();
}
/**
* @deprecated Use {@link #setBorderColor(int)} instead
*/
@Deprecated
public void setBorderColorResource(@ColorRes int borderColorRes) {
setBorderColor(getContext().getResources().getColor(borderColorRes));
}
public int getCircleBackgroundColor() {
return mCircleBackgroundColor;
}
public void setCircleBackgroundColor(@ColorInt int circleBackgroundColor) {
if (circleBackgroundColor == mCircleBackgroundColor) {
return;
}
mCircleBackgroundColor = circleBackgroundColor;
mCircleBackgroundPaint.setColor(circleBackgroundColor);
invalidate();
}
public void setCircleBackgroundColorResource(@ColorRes int circleBackgroundRes) {
setCircleBackgroundColor(getContext().getResources().getColor(circleBackgroundRes));
}
/**
* Return the color drawn behind the circle-shaped drawable.
*
* @return The color drawn behind the drawable
* @deprecated Use {@link #getCircleBackgroundColor()} instead.
*/
@Deprecated
public int getFillColor() {
return getCircleBackgroundColor();
}
/**
* Set mergeBitmapRecycle color to be drawn behind the circle-shaped drawable. Note that
* this has no effect if the drawable is opaque or no drawable is set.
*
* @param fillColor The color to be drawn behind the drawable
* @deprecated Use {@link #setCircleBackgroundColor(int)} instead.
*/
@Deprecated
public void setFillColor(@ColorInt int fillColor) {
setCircleBackgroundColor(fillColor);
}
/**
* Set mergeBitmapRecycle color to be drawn behind the circle-shaped drawable. Note that
* this has no effect if the drawable is opaque or no drawable is set.
*
* @param fillColorRes The color resource to be resolved to mergeBitmapRecycle color and
* drawn behind the drawable
* @deprecated Use {@link #setCircleBackgroundColorResource(int)} instead.
*/
@Deprecated
public void setFillColorResource(@ColorRes int fillColorRes) {
setCircleBackgroundColorResource(fillColorRes);
}
public int getBorderWidth() {
return mBorderWidth;
}
public void setBorderWidth(int borderWidth) {
if (borderWidth == mBorderWidth) {
return;
}
mBorderWidth = borderWidth;
setup();
}
public boolean isBorderOverlay() {
return mBorderOverlay;
}
public void setBorderOverlay(boolean borderOverlay) {
if (borderOverlay == mBorderOverlay) {
return;
}
mBorderOverlay = borderOverlay;
setup();
}
public boolean isDisableCircularTransformation() {
return mDisableCircularTransformation;
}
public void setDisableCircularTransformation(boolean disableCircularTransformation) {
if (mDisableCircularTransformation == disableCircularTransformation) {
return;
}
mDisableCircularTransformation = disableCircularTransformation;
initializeBitmap();
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
initializeBitmap();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
initializeBitmap();
}
@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
initializeBitmap();
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
initializeBitmap();
}
@Override
public void setColorFilter(ColorFilter cf) {
if (cf == mColorFilter) {
return;
}
mColorFilter = cf;
applyColorFilter();
invalidate();
}
@Override
public ColorFilter getColorFilter() {
return mColorFilter;
}
private void applyColorFilter() {
if (mBitmapPaint != null) {
mBitmapPaint.setColorFilter(mColorFilter);
}
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private void initializeBitmap() {
if (mDisableCircularTransformation) {
mBitmap = null;
} else {
mBitmap = getBitmapFromDrawable(getDrawable());
}
setup();
}
private void setup() {
if (!mReady) {
mSetupPending = true;
return;
}
if (getWidth() == 0 && getHeight() == 0) {
return;
}
if (mBitmap == null) {
invalidate();
return;
}
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
mCircleBackgroundPaint.setStyle(Paint.Style.FILL);
mCircleBackgroundPaint.setAntiAlias(true);
mCircleBackgroundPaint.setColor(mCircleBackgroundColor);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(calculateBounds());
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay && mBorderWidth > 0) {
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
}
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
applyColorFilter();
updateShaderMatrix();
invalidate();
}
private RectF calculateBounds() {
int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int sideLength = Math.min(availableWidth, availableHeight);
float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
return new RectF(left, top, left + sideLength, top + sideLength);
}
private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return inTouchableArea(event.getX(), event.getY()) && super.onTouchEvent(event);
}
private boolean inTouchableArea(float x, float y) {
return Math.pow(x - mBorderRect.centerX(), 2) + Math.pow(y - mBorderRect.centerY(), 2) <= Math.pow(mBorderRadius, 2);
}
private class OutlineProvider extends ViewOutlineProvider {
@Override
public void getOutline(View view, Outline outline) {
Rect bounds = new Rect();
mBorderRect.roundOut(bounds);
outline.setRoundRect(bounds, bounds.width() / 2.0f);
}
}
public void setImage(int id, int color) {
this.mBorderColor = color;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), id);
setImageBitmap(BitmapUtil.changeBitmapColor(bitmap, color));
}
}

View File

@ -0,0 +1,186 @@
package com.za.signature.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.util.DisplayUtil;
/**
* 自定义圆形View
*
* @author king
* @since 2018-06-01
*/
public class CircleView extends View {
private Paint mPaint;
private Paint backPaint;
private Paint borderPaint;
private Paint outBorderPaint;
private int paintColor;
private int outBorderColor = Color.parseColor("#0c53ab");
private int circleRadius;
private int radiusLevel;
private boolean showBorder = false;
private boolean showOutBorder = false;
public CircleView(Context context) {
this(context, null);
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
paintColor = ta.getColor(R.styleable.CircleView_penColor, PenConfig.PAINT_COLOR);
outBorderColor = ta.getColor(R.styleable.CircleView_penColor, Color.parseColor("#0c53ab"));
radiusLevel = ta.getInteger(R.styleable.CircleView_sizeLevel, 2);
circleRadius = DisplayUtil.dip2px(context, PaintSettingWindow.PEN_SIZES[radiusLevel]);
showBorder = ta.getBoolean(R.styleable.CircleView_showBorder, false);
showOutBorder = ta.getBoolean(R.styleable.CircleView_showOutBorder, false);
ta.recycle();
init();
}
private void init() {
borderPaint = new Paint();
borderPaint.setColor(paintColor);
borderPaint.setStrokeWidth(5);
borderPaint.setAntiAlias(true);
borderPaint.setStrokeJoin(Paint.Join.ROUND);
borderPaint.setStyle(Paint.Style.STROKE);
outBorderPaint = new Paint();
outBorderPaint.setColor(outBorderColor);
outBorderPaint.setStrokeWidth(3.5f);
outBorderPaint.setAntiAlias(true);
outBorderPaint.setStrokeJoin(Paint.Join.ROUND);
outBorderPaint.setStyle(Paint.Style.STROKE);
backPaint = new Paint();
backPaint.setColor(Color.WHITE);
backPaint.setAntiAlias(true);
backPaint.setStrokeJoin(Paint.Join.ROUND);
backPaint.setStyle(Paint.Style.FILL);
mPaint = new Paint();
mPaint.setColor(paintColor);
mPaint.setStrokeWidth(20);
mPaint.setAntiAlias(true);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle((float) getWidth() / 2, (float) getHeight() / 2, (float) getWidth() / 2, backPaint);
//绘制内边框
if (showBorder) {
canvas.drawCircle((float) getWidth() / 2, (float) getHeight() / 2, circleRadius / 2.5f + 10, borderPaint);
}
//绘制外边框
if (showOutBorder) {
canvas.drawCircle((float) getWidth() / 2, (float) getHeight() / 2, (float) getWidth() / 2 - 2f, outBorderPaint);
}
canvas.drawCircle((float) getWidth() / 2, (float) getHeight() / 2, circleRadius / 2.5f, mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = onMeasureR(0, widthMeasureSpec);
int height = onMeasureR(1, heightMeasureSpec);
setMeasuredDimension(width, height);
}
/**
* 计算控件宽高
*/
public int onMeasureR(int attr, int oldMeasure) {
int newSize = 0;
int mode = MeasureSpec.getMode(oldMeasure);
int oldSize = MeasureSpec.getSize(oldMeasure);
switch (mode) {
case MeasureSpec.EXACTLY:
newSize = oldSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
float value;
if (attr == 0) {
if (showOutBorder) {
value = (circleRadius / 2.5f + 40) * 2;
} else {
value = (circleRadius / 2.5f + 25) * 2;
}
newSize = (int) (getPaddingLeft() + value + getPaddingRight());
} else if (attr == 1) {
if (showOutBorder) {
value = (circleRadius / 2.5f + 40) * 2;
} else {
value = (circleRadius / 2.5f + 25) * 2;
}
// value = (circleRadius / 2.5f + 20) * 2;
// 控件的高度 + getPaddingTop() + getPaddingBottom()
newSize = (int) (getPaddingTop() + value + getPaddingBottom());
}
break;
default:
break;
}
return newSize;
}
public void setPaintColor(int paintColor) {
this.paintColor = paintColor;
mPaint.setColor(paintColor);
invalidate();
}
public void setRadiusLevel(int level) {
this.radiusLevel = level;
this.circleRadius = DisplayUtil.dip2px(getContext(), PaintSettingWindow.PEN_SIZES[level]);
invalidate();
}
public void showBorder(boolean showBorder) {
this.showBorder = showBorder;
invalidate();
}
public void setOutBorderColor(int outBorderColor) {
this.outBorderColor = outBorderColor;
outBorderPaint.setColor(outBorderColor);
invalidate();
}
public int getPaintColor() {
return paintColor;
}
public int getRadiusLevel() {
return radiusLevel;
}
}

View File

@ -0,0 +1,69 @@
package com.za.signature.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.za.signature.util.StatusBarCompat;
/***
* 名称EraserView
* 描述:橡皮擦指示器
* 最近修改时间2018年09月13日 16:57分
* @since 2018-09-13
* @author king
*/
public class EraserView extends View {
private Paint paint;
private int statusBarHeight;
public EraserView(Context context) {
this(context, null);
}
public EraserView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public EraserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setBackgroundDrawable(null);
setBackground(null);
setDrawingCacheEnabled(false);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
if (Build.MODEL.contains("EBEN")) {//E人E本顶部没有状态栏
statusBarHeight = 0;
} else {
statusBarHeight = StatusBarCompat.getStatusBarHeight(context);
}
}
@Override
public void setX(float x) {
super.setX(x - (float) getWidth() / 2);
}
@Override
public void setY(float y) {
super.setY(y - (float) getHeight() / 2 - statusBarHeight);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
paint.setColor(Color.LTGRAY);
canvas.drawCircle((float) getWidth() / 2, (float) getHeight() / 2, (float) getWidth() / 2, paint);
paint.setColor(Color.WHITE);
canvas.drawCircle((float) getWidth() / 2, (float) getHeight() / 2, (float) getWidth() / 2 - 8, paint);
}
}

View File

@ -0,0 +1,121 @@
package com.za.signature.view;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/***
* 名称GridDrawable<br>
* 描述:自定义米格背景视图
* 最近修改时间:
* @since 2017/11/16
* @author king
*/
public class GridDrawable extends Drawable {
private Paint mPaint;
private Paint mDashPaint;
private Paint mLinePaint;
private Bitmap mBitmap;
private Canvas mCanvas;
private Path linePath;
private Path dashPath;
private final int mWidth;
private final int mHeight;
private final int backgroundColor;
public GridDrawable(int width, int height, int backgroundColor) {
this.mWidth = width;
this.mHeight = height;
this.backgroundColor = backgroundColor;
init();
}
public void init() {
//边框画笔
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(10.0f);
mPaint.setColor(Color.parseColor("#c4c4c4"));
//虚线画笔
mDashPaint = new Paint();
mDashPaint.setStyle(Paint.Style.STROKE);
mDashPaint.setAntiAlias(true);
mDashPaint.setStrokeWidth(5.0f);
mDashPaint.setColor(Color.parseColor("#c4c4c4"));
mDashPaint.setPathEffect(new DashPathEffect(new float[]{50, 40}, 0));
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setAntiAlias(true);
mLinePaint.setStrokeWidth(5.0f);
mLinePaint.setColor(Color.parseColor("#c4c4c4"));
linePath = new Path();
dashPath = new Path();
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(mBitmap);
mCanvas.drawColor(backgroundColor);
doDraw();
}
private void doDraw() {
Rect rect = new Rect(0, 0, mWidth, mHeight);
mCanvas.drawRect(rect, mPaint);
linePath.moveTo(0, (float) mHeight / 2);
linePath.lineTo(mWidth, (float) mHeight / 2);
mCanvas.drawPath(linePath, mLinePaint);
linePath.moveTo((float) mWidth / 2, 0);
linePath.lineTo((float) mWidth / 2, mHeight);
mCanvas.drawPath(linePath, mLinePaint);
dashPath.moveTo(0, 0);
dashPath.lineTo(mWidth, mHeight);
mCanvas.drawPath(dashPath, mDashPaint);
dashPath.moveTo(mWidth, 0);
dashPath.lineTo(0, mHeight);
mCanvas.drawPath(dashPath, mDashPaint);
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
}
@Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
mDashPaint.setAlpha(i);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
mDashPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}

View File

@ -0,0 +1,221 @@
package com.za.signature.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.pen.BasePen;
import com.za.signature.pen.SteelPen;
import com.za.signature.util.BitmapUtil;
import com.za.signature.util.DisplayUtil;
/**
* 田字格手写画板
*
* @author king
* @since 2018/5/4
*/
public class GridPaintView extends View {
private Paint mPaint;
private Canvas mCanvas;
private Bitmap mBitmap;
private BasePen mStokeBrushPen;
/**
* 是否有绘制
*/
private boolean hasDraw;
/**
* 画布宽度
*/
private final int mWidth;
/**
* 画布高度
*/
private final int mHeight;
public GridPaintView(Context context) {
this(context, null);
}
public GridPaintView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GridPaintView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mWidth = (int) getResources().getDimension(R.dimen.sign_grid_size);
mHeight = (int) getResources().getDimension(R.dimen.sign_grid_size);
initParameter();
}
private void initParameter() {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mStokeBrushPen = new SteelPen();
initPaint();
initCanvas();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(PenConfig.PAINT_COLOR);
mPaint.setStrokeWidth(DisplayUtil.dip2px(getContext(), PaintSettingWindow.PEN_SIZES[PenConfig.PAINT_SIZE_LEVEL]));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAlpha(0xFF);
mPaint.setAntiAlias(true);
mPaint.setStrokeMiter(1.0f);
mStokeBrushPen.setPaint(mPaint);
}
private void initCanvas() {
mCanvas = new Canvas(mBitmap);
//设置画布的颜色的问题
mCanvas.drawColor(Color.TRANSPARENT);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
mStokeBrushPen.draw(canvas);
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mStokeBrushPen.onTouchEvent(event, mCanvas);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (mGetWriteListener != null) {
mGetWriteListener.onWriteStart();
}
break;
case MotionEvent.ACTION_MOVE:
hasDraw = true;
if (mGetWriteListener != null) {
mGetWriteListener.onWriteStart();
}
break;
case MotionEvent.ACTION_UP:
if (mGetWriteListener != null) {
mGetWriteListener.onWriteCompleted(System.currentTimeMillis());
}
break;
default:
break;
}
invalidate();
return true;
}
/**
* @return 判断是否有绘制内容在画布上
*/
public boolean isEmpty() {
return !hasDraw;
}
/**
* 清除画布
*/
public void reset() {
mBitmap.eraseColor(Color.TRANSPARENT);
hasDraw = false;
mStokeBrushPen.clear();
invalidate();
}
public void release() {
destroyDrawingCache();
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
}
public WriteListener mGetWriteListener;
public void setGetTimeListener(WriteListener l) {
mGetWriteListener = l;
}
public interface WriteListener {
/**
* 开始书写
*/
void onWriteStart();
/**
* 书写完毕
*
* @param time 当前时间
*/
void onWriteCompleted(long time);
}
/**
* 设置画笔大小
*
* @param width 大小
*/
public void setPaintWidth(int width) {
if (mPaint != null) {
mPaint.setStrokeWidth(DisplayUtil.dip2px(getContext(), width));
mStokeBrushPen.setPaint(mPaint);
invalidate();
}
}
/**
* 设置画笔颜色
*
* @param color 颜色
*/
public void setPaintColor(int color) {
if (mPaint != null) {
mPaint.setColor(color);
mStokeBrushPen.setPaint(mPaint);
invalidate();
}
}
/**
* 构建Bitmap
*
* @return 所绘制的bitmap
*/
public Bitmap buildBitmap(boolean clearBlank, int zoomSize) {
if (!hasDraw) {
return null;
}
Bitmap result = BitmapUtil.zoomImg(mBitmap, zoomSize);
if (clearBlank) {
result = BitmapUtil.clearLRBlank(result, 10, Color.TRANSPARENT);
}
return result;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,201 @@
package com.za.signature.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.inputmethod.EditorInfo;
import androidx.appcompat.widget.AppCompatEditText;
import com.za.signature.util.DisplayUtil;
import com.za.signature.util.SystemUtil;
/**
* 显示手写字的View
*
* @author king
* @since 2018-06-28
*/
public class HandWriteEditView extends AppCompatEditText {
private float lineHeight = 150f;
boolean reLayout = false;
private TextWatch textWatcher;
public HandWriteEditView(Context context) {
super(context);
init();
}
public HandWriteEditView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HandWriteEditView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
this.setTextIsSelectable(false);
this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
this.setGravity(Gravity.START);
//禁止选择复制粘贴
SystemUtil.disableCopyAndPaste(this);
lineHeight = (float) DisplayUtil.dip2px(getContext(), 50f);
addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
float add = lineHeight;
setLineSpacing(0f, 1f);
setLineSpacing(add, 0);
setIncludeFontPadding(false);
setGravity(Gravity.CENTER_VERTICAL);
int top = (int) ((add - getTextSize()) * 0.5f);
setPadding(getPaddingLeft(), top, getPaddingRight(), -top);
}
@Override
public void afterTextChanged(Editable editable) {
if (textWatcher != null) {
textWatcher.afterTextChanged(editable);
}
}
});
}
/**
* 设置行高
*
*/
public void setLineHeight(float lineHeight) {
this.lineHeight = lineHeight;
invalidate();
}
/**
* 添加手写文字
*
* @param srcBitmap 手写文字图片
*/
public Editable addBitmapToText(Bitmap srcBitmap) {
if (srcBitmap == null) {
return null;
}
SpannableString mSpan = new SpannableString("1");
mSpan.setSpan(new ImageSpan(getContext(), srcBitmap), mSpan.length() - 1, mSpan.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Editable editable = getText();
//获取光标所在位置
int index = getSelectionStart();
editable.insert(index, mSpan);
setText(editable);
setSelection(index + mSpan.length());
return editable;
}
/**
* 添加空格
*/
public void addSpace(int fontSize) {
int size = DisplayUtil.dip2px(getContext(), fontSize);
ColorDrawable drawable = new ColorDrawable(Color.TRANSPARENT);
Bitmap bitmap = Bitmap.createBitmap(fontSize, size, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);
drawable.draw(canvas);
addBitmapToText(bitmap);
}
/**
* 删除文字
*/
public Editable deleteBitmapFromText() {
Editable editable = getEditableText();
int start = getSelectionStart();
if (start == 0) {
return null;
}
editable.delete(start - 1, start);
return editable;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!reLayout) {
reLayout = true;
setIncludeFontPadding(false);
setGravity(Gravity.CENTER_VERTICAL);
setLineSpacing(lineHeight, 0);
int top = (int) ((lineHeight - getTextSize()) * 0.5f);
setPadding(getPaddingLeft(), top, getPaddingRight(), -top);
requestLayout();
invalidate();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(expandSpec, expandSpec);
}
boolean canPaste() {
return false;
}
boolean canCut() {
return false;
}
boolean canCopy() {
return false;
}
boolean canSelectAllText() {
return false;
}
boolean canSelectText() {
return false;
}
boolean textCanBeSelected() {
return false;
}
@Override
public boolean isSuggestionsEnabled() {
return false;
}
public void addTextWatcher(TextWatch textWatcher) {
this.textWatcher = textWatcher;
}
public interface TextWatch {
void afterTextChanged(Editable var1);
}
}

View File

@ -0,0 +1,172 @@
package com.za.signature.view;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
/**
* 画笔设置窗口
*
* @author king
* @since 2018-06-04
*/
public class PaintSettingWindow extends PopupWindow {
public static final String[] PEN_COLORS = new String[]{"#101010", "#027de9", "#0cba02", "#f9d403", "#ec041f"};
public static final int[] PEN_SIZES = new int[]{5, 15, 20, 25, 30};
private final Context context;
private CircleView lastSelectColorView;
private CircleView lastSelectSizeView;
private int selectColor;
private View rootView;
private OnSettingListener settingListener;
public PaintSettingWindow(Context context) {
super(context);
this.context = context;
init();
}
private void init() {
rootView = LayoutInflater.from(context).inflate(R.layout.sign_paint_setting, null);
LinearLayout container = rootView.findViewById(R.id.color_container);
for (int i = 0; i < container.getChildCount(); i++) {
final int index = i;
final CircleView circleView = (CircleView) container.getChildAt(i);
if (circleView.getPaintColor() == PenConfig.PAINT_COLOR) {
circleView.showBorder(true);
lastSelectColorView = circleView;
}
circleView.setOnClickListener(v -> {
if (lastSelectColorView != null) {
lastSelectColorView.showBorder(false);
}
circleView.showBorder(true);
selectColor = Color.parseColor(PEN_COLORS[index]);
lastSelectColorView = circleView;
PenConfig.PAINT_COLOR = selectColor;
PenConfig.setPaintColor(context, selectColor);
if (settingListener != null) {
settingListener.onColorSetting(selectColor);
}
});
}
LinearLayout sizeContainer = rootView.findViewById(R.id.size_container);
for (int i = 0; i < sizeContainer.getChildCount(); i++) {
final int index = i;
final CircleView circleView = (CircleView) sizeContainer.getChildAt(i);
if (circleView.getRadiusLevel() == PenConfig.PAINT_SIZE_LEVEL) {
circleView.showBorder(true);
lastSelectSizeView = circleView;
}
circleView.setOnClickListener(v -> {
if (lastSelectSizeView != null) {
lastSelectSizeView.showBorder(false);
}
circleView.showBorder(true);
lastSelectSizeView = circleView;
PenConfig.PAINT_SIZE_LEVEL = index;
PenConfig.savePaintTextLevel(context, index);
if (settingListener != null) {
settingListener.onSizeSetting(index);
}
});
}
this.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
this.setContentView(rootView);
this.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
this.setFocusable(true);
this.setOutsideTouchable(true);
this.update();
}
/**
* 显示在左上角
*/
public void popAtTopLeft() {
View sv = rootView.findViewById(R.id.size_container);
View cv = rootView.findViewById(R.id.color_container);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(20, 10, 20, 0);
lp.gravity = Gravity.CENTER;
sv.setLayoutParams(lp);
LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp1.setMargins(20, 0, 20, 40);
lp1.gravity = Gravity.CENTER;
cv.setLayoutParams(lp1);
rootView.setBackgroundResource(R.mipmap.sign_top_left_pop_bg);
}
/**
* 显示在右上角
*/
public void popAtTopRight() {
View sv = rootView.findViewById(R.id.size_container);
View cv = rootView.findViewById(R.id.color_container);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(20, 10, 20, 0);
lp.gravity = Gravity.CENTER;
sv.setLayoutParams(lp);
LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp1.setMargins(20, 0, 20, 40);
lp1.gravity = Gravity.CENTER;
cv.setLayoutParams(lp1);
rootView.setBackgroundResource(R.mipmap.sign_top_right_pop_bg);
}
/**
* 显示在右下角
*/
public void popAtBottomRight() {
View sv = rootView.findViewById(R.id.size_container);
View cv = rootView.findViewById(R.id.color_container);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(20, 40, 20, 0);
lp.gravity = Gravity.CENTER;
sv.setLayoutParams(lp);
LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp1.setMargins(20, 0, 20, 10);
lp1.gravity = Gravity.CENTER;
cv.setLayoutParams(lp1);
rootView.setBackgroundResource(R.mipmap.sign_bottom_right_pop_bg);
}
/**
* 显示在左边
*/
public void popAtLeft() {
View sv = rootView.findViewById(R.id.size_container);
View cv = rootView.findViewById(R.id.color_container);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(40, 20, 55, 0);
lp.gravity = Gravity.CENTER;
sv.setLayoutParams(lp);
LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lp1.setMargins(40, 0, 55, 20);
lp1.gravity = Gravity.CENTER;
cv.setLayoutParams(lp1);
rootView.setBackgroundResource(R.mipmap.sign_left_pop_bg);
}
public interface OnSettingListener {
void onColorSetting(int color);
void onSizeSetting(int index);
}
public void setSettingListener(OnSettingListener settingListener) {
this.settingListener = settingListener;
}
}

View File

@ -0,0 +1,504 @@
package com.za.signature.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import com.za.servicing.R;
import com.za.signature.config.PenConfig;
import com.za.signature.pen.BasePen;
import com.za.signature.pen.Eraser;
import com.za.signature.pen.SteelPen;
import com.za.signature.util.BitmapUtil;
import com.za.signature.util.DisplayUtil;
import com.za.signature.util.StepOperator;
/**
* 手写画板
*
* @author king
* @since 2018/5/4
*/
public class PaintView extends View {
public static final int TYPE_PEN = 0;
public static final int TYPE_ERASER = 1;
private Paint mPaint;
private Canvas mCanvas;
private Bitmap mBitmap;
private BasePen mStokeBrushPen;
/**
* 是否允许写字
*/
private boolean isFingerEnable = true;
/**
* 是否橡皮擦模式
*/
private boolean isEraser = false;
/**
* 是否有绘制
*/
private boolean hasDraw = false;
/**
* 画笔轨迹记录
*/
private StepOperator mStepOperation;
private StepCallback mCallback;
/**
* 是否可以撤销
*/
private boolean mCanUndo;
/**
* 是否可以恢复
*/
private boolean mCanRedo;
private int mWidth;
private int mHeight;
private boolean isDrawing = false;//是否正在绘制
private int toolType = 0; //记录手写笔类型:触控笔/手指
private Eraser eraser;
public PaintView(Context context) {
this(context, null);
}
public PaintView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PaintView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 初始化画板
*
* @param width 画板宽度
* @param height 画板高度
* @param path 初始图片路径
*/
public void init(int width, int height, String path) {
this.mWidth = width;
this.mHeight = height;
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_4444);
mStokeBrushPen = new SteelPen();
initPaint();
initCanvas();
mStepOperation = new StepOperator();
if (!TextUtils.isEmpty(path)) {
Bitmap bitmap = BitmapFactory.decodeFile(path);
resize(bitmap, mWidth, mHeight);
} else {
mStepOperation.addBitmap(mBitmap);
}
//橡皮擦
eraser = new Eraser(getResources().getDimensionPixelSize(R.dimen.sign_eraser_size));
}
/**
* 初始画笔设置
*/
private void initPaint() {
int strokeWidth = DisplayUtil.dip2px(getContext(), PaintSettingWindow.PEN_SIZES[PenConfig.PAINT_SIZE_LEVEL]);
mPaint = new Paint();
mPaint.setColor(PenConfig.PAINT_COLOR);
mPaint.setStrokeWidth(strokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAlpha(0xFF);
mPaint.setAntiAlias(true);
mPaint.setStrokeMiter(1.0f);
mStokeBrushPen.setPaint(mPaint);
}
private void initCanvas() {
mCanvas = new Canvas(mBitmap);
//设置画布的背景色为透明
mCanvas.drawColor(Color.TRANSPARENT);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
if (!isEraser) {
mStokeBrushPen.draw(canvas);
}
super.onDraw(canvas);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
toolType = event.getToolType(event.getActionIndex());
if (!isFingerEnable && toolType != MotionEvent.TOOL_TYPE_STYLUS) {
return false;
}
if (isEraser) {
eraser.handleEraserEvent(event, mCanvas);
} else {
mStokeBrushPen.onTouchEvent(event, mCanvas);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
isDrawing = false;
break;
case MotionEvent.ACTION_MOVE:
hasDraw = true;
mCanUndo = true;
isDrawing = true;
break;
case MotionEvent.ACTION_CANCEL:
isDrawing = false;
break;
case MotionEvent.ACTION_UP:
if (mStepOperation != null && isDrawing) {
mStepOperation.addBitmap(mBitmap);
}
mCanUndo = !mStepOperation.currentIsFirst();
mCanRedo = !mStepOperation.currentIsLast();
if (mCallback != null) {
mCallback.onOperateStatusChanged();
}
isDrawing = false;
break;
default:
break;
}
invalidate();
return true;
}
/**
* @return 判断是否有绘制内容在画布上
*/
public boolean isEmpty() {
return !hasDraw;
}
/**
* 撤销
*/
public void undo() {
if (mStepOperation == null || !mCanUndo) {
return;
}
if (!mStepOperation.currentIsFirst()) {
mCanUndo = true;
mStepOperation.undo(mBitmap);
hasDraw = true;
invalidate();
if (mStepOperation.currentIsFirst()) {
mCanUndo = false;
hasDraw = false;
}
} else {
mCanUndo = false;
hasDraw = false;
}
if (!mStepOperation.currentIsLast()) {
mCanRedo = true;
}
if (mCallback != null) {
mCallback.onOperateStatusChanged();
}
}
/**
* 恢复
*/
public void redo() {
if (mStepOperation == null || !mCanRedo) {
return;
}
if (!mStepOperation.currentIsLast()) {
mCanRedo = true;
mStepOperation.redo(mBitmap);
hasDraw = true;
invalidate();
if (mStepOperation.currentIsLast()) {
mCanRedo = false;
}
} else {
mCanRedo = false;
}
if (!mStepOperation.currentIsFirst()) {
mCanUndo = true;
}
if (mCallback != null) {
mCallback.onOperateStatusChanged();
}
}
/**
* 清除画布,记得清除点的集合
*/
public void reset() {
mBitmap.eraseColor(Color.TRANSPARENT);
hasDraw = false;
mStokeBrushPen.clear();
if (mStepOperation != null) {
mStepOperation.reset();
mStepOperation.addBitmap(mBitmap);
}
mCanRedo = false;
mCanUndo = false;
if (mCallback != null) {
mCallback.onOperateStatusChanged();
}
invalidate();
}
public void release() {
destroyDrawingCache();
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
if (mStepOperation != null) {
mStepOperation.freeBitmaps();
mStepOperation = null;
}
}
public interface StepCallback {
/**
* 操作变更
*/
void onOperateStatusChanged();
}
public void setStepCallback(StepCallback callback) {
this.mCallback = callback;
}
/**
* 设置画笔样式
*
*/
public void setPenType(int penType) {
isEraser = false;
switch (penType) {
case TYPE_PEN:
mStokeBrushPen = new SteelPen();
break;
case TYPE_ERASER:
isEraser = true;
break;
}
//设置
if (mStokeBrushPen.isNullPaint()) {
mStokeBrushPen.setPaint(mPaint);
}
invalidate();
}
/**
* 设置画笔大小
*
* @param width 大小
*/
public void setPaintWidth(int width) {
if (mPaint != null) {
mPaint.setStrokeWidth(DisplayUtil.dip2px(getContext(), width));
// eraser.setPaintWidth(DisplayUtil.dip2px(getContext(), width));
mStokeBrushPen.setPaint(mPaint);
invalidate();
}
}
/**
* 设置画笔颜色
*
* @param color 颜色
*/
public void setPaintColor(int color) {
if (mPaint != null) {
mPaint.setColor(color);
mStokeBrushPen.setPaint(mPaint);
invalidate();
}
}
/**
* 构建Bitmap
*
* @return 所绘制的bitmap
*/
public Bitmap buildAreaBitmap(boolean isCrop) {
if (!hasDraw) {
return null;
}
Bitmap result;
if (isCrop) {
result = BitmapUtil.clearBlank(mBitmap, 50, Color.TRANSPARENT);
} else {
result = mBitmap;
}
destroyDrawingCache();
return result;
}
public boolean isFingerEnable() {
return isFingerEnable;
}
public void setFingerEnable(boolean fingerEnable) {
isFingerEnable = fingerEnable;
}
public boolean isEraser() {
return isEraser;
}
public boolean canUndo() {
return mCanUndo;
}
public boolean canRedo() {
return mCanRedo;
}
public Bitmap getBitmap() {
return mBitmap;
}
/**
* 图片大小调整适配画布宽高
*
* @param bitmap 源图
* @param width 新宽度
* @param height 新高度
*/
public void resize(Bitmap bitmap, int width, int height) {
if (mBitmap != null) {
if (width >= this.mWidth) {
height = width * mBitmap.getHeight() / mBitmap.getWidth();
}
this.mWidth = width;
this.mHeight = height;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
restoreLastBitmap(bitmap, mBitmap);
initCanvas();
if (mStepOperation != null) {
mStepOperation.addBitmap(mBitmap);
}
invalidate();
}
}
/**
* 恢复最后画的bitmap
*
* @param srcBitmap 最后的bitmap
* @param newBitmap 新bitmap
*/
private void restoreLastBitmap(Bitmap srcBitmap, Bitmap newBitmap) {
try {
if (srcBitmap == null || srcBitmap.isRecycled()) {
return;
}
srcBitmap = BitmapUtil.zoomImg(srcBitmap, newBitmap.getWidth());
//缩放后如果还是超出新图宽高,继续缩放
if (srcBitmap.getWidth() > newBitmap.getWidth() || srcBitmap.getHeight() > newBitmap.getHeight()) {
srcBitmap = BitmapUtil.zoomImage(srcBitmap, newBitmap.getWidth(), newBitmap.getHeight());
}
//保存所有的像素的数组,图片宽×高
int[] pixels = new int[srcBitmap.getWidth() * srcBitmap.getHeight()];
srcBitmap.getPixels(pixels, 0, srcBitmap.getWidth(), 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
newBitmap.setPixels(pixels, 0, srcBitmap.getWidth(), 0, 0,
srcBitmap.getWidth(), srcBitmap.getHeight());
} catch (OutOfMemoryError e) {
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = onMeasureR(0, widthMeasureSpec);
int height = onMeasureR(1, heightMeasureSpec);
setMeasuredDimension(width, height);
}
/**
* 计算控件宽高
*/
public int onMeasureR(int attr, int oldMeasure) {
int newSize = 0;
int mode = MeasureSpec.getMode(oldMeasure);
int oldSize = MeasureSpec.getSize(oldMeasure);
switch (mode) {
case MeasureSpec.EXACTLY:
newSize = oldSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
float value = mWidth;
if (attr == 0) {
if (mBitmap != null) {
value = mBitmap.getWidth();
}
// 控件的宽度
newSize = (int) (getPaddingLeft() + value + getPaddingRight());
} else if (attr == 1) {
value = mHeight;
if (mBitmap != null) {
value = mBitmap.getHeight();
}
// 控件的高度
newSize = (int) (getPaddingTop() + value + getPaddingBottom());
}
break;
default:
break;
}
return newSize;
}
public Bitmap getLastBitmap() {
return mBitmap;
}
}