16 | * 默认选中的颜色为第一个
17 | */
18 | public class CustomColorGroup extends ColorGroup {
19 | public CustomColorGroup(Context context) {
20 | this(context, null);
21 | }
22 |
23 | public CustomColorGroup(Context context, AttributeSet attrs) {
24 | super(context, attrs);
25 | initColors(context, attrs);
26 | }
27 |
28 | private void initColors(Context context, AttributeSet attrs) {
29 | if (attrs == null) {
30 | return;
31 | }
32 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomColorGroup);
33 | boolean hasExtraType = a.getBoolean(R.styleable.CustomColorGroup_hasExtraType, false);
34 | a.recycle();
35 |
36 | int size = getContext().getResources().getDimensionPixelSize(R.dimen.image_color);
37 | int margin = getContext().getResources().getDimensionPixelSize(R.dimen.image_color_margin);
38 | if (hasExtraType) {
39 | addColorRadio(size, margin, ColorRadio.TYPE_MOSAIC_COLOR);
40 | }
41 | int[] doodleColors = PictureEditor.getInstance().getDoodleColors(getContext());
42 | for (int color : doodleColors) {
43 | addColorRadio(size, margin, color);
44 | }
45 | setCheckColor(doodleColors[0]);
46 | }
47 |
48 | private void addColorRadio(int size, int margin, int color) {
49 | ColorRadio colorRadio = new ColorRadio(getContext());
50 | colorRadio.setColor(color);
51 | colorRadio.setClickable(true);
52 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(size, size);
53 | params.leftMargin = margin;
54 | params.rightMargin = margin;
55 | params.topMargin = margin;
56 | params.bottomMargin = margin;
57 | addView(colorRadio, params);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/sticker/ImageStickerView.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.sticker;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.ImageView;
7 |
8 | import androidx.annotation.DrawableRes;
9 |
10 | import com.vachel.editor.R;
11 | import com.vachel.editor.ui.sticker.StickerView;
12 |
13 | public class ImageStickerView extends StickerView {
14 |
15 | private ImageView mImageView;
16 | private @DrawableRes int mImageResource = R.mipmap.ic_doodle_checked;
17 |
18 | public ImageStickerView(Context context) {
19 | super(context);
20 | }
21 |
22 | public ImageStickerView(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 | public ImageStickerView(Context context, AttributeSet attrs, int defStyleAttr) {
27 | super(context, attrs, defStyleAttr);
28 | }
29 |
30 | @Override
31 | public View onCreateContentView(Context context) {
32 | mImageView = new ImageView(context);
33 | mImageView.setImageResource(mImageResource);
34 | return mImageView;
35 | }
36 |
37 | public void setImage(@DrawableRes int image){
38 | mImageResource = image;
39 | mImageView.setImageResource(image);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/sticker/TextStickerView.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.sticker;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.graphics.drawable.GradientDrawable;
6 | import android.util.AttributeSet;
7 | import android.util.TypedValue;
8 | import android.view.View;
9 | import android.widget.TextView;
10 |
11 | import com.vachel.editor.bean.StickerText;
12 | import com.vachel.editor.ui.widget.TextEditDialog;
13 | import com.vachel.editor.util.Utils;
14 |
15 | public class TextStickerView extends StickerView implements TextEditDialog.ITextChangedListener {
16 | private static final int PADDING = 26;
17 | private static final int PADDING_BG = 20;
18 | private static final float TEXT_SIZE_SP = 30f;
19 |
20 | private TextView mTextView;
21 | private StickerText mText;
22 | private TextEditDialog mDialog;
23 | private boolean mAllowEditText = true;
24 |
25 | public TextStickerView(Context context) {
26 | this(context, null, 0);
27 | }
28 |
29 | public TextStickerView(Context context, AttributeSet attrs) {
30 | this(context, attrs, 0);
31 | }
32 |
33 | public TextStickerView(Context context, AttributeSet attrs, int defStyleAttr) {
34 | super(context, attrs, defStyleAttr);
35 | }
36 |
37 | @Override
38 | public View onCreateContentView(Context context) {
39 | mTextView = new TextView(context);
40 | mTextView.setTextSize(TEXT_SIZE_SP);
41 | mTextView.setPadding(PADDING, PADDING, PADDING, PADDING);
42 | mTextView.setTextColor(Color.WHITE);
43 | GradientDrawable gd = new GradientDrawable();//创建drawable
44 | gd.setColor(Color.TRANSPARENT);
45 | gd.setCornerRadius(Utils.dip2px(context, 10));
46 | mTextView.setBackground(gd);
47 | return mTextView;
48 | }
49 |
50 | public StickerText getText() {
51 | return mText;
52 | }
53 |
54 | @Override
55 | public void onContentTap() {
56 | if (mAllowEditText){
57 | TextEditDialog dialog = getDialog();
58 | dialog.setText(mText);
59 | dialog.show();
60 | }
61 | }
62 |
63 | public void enableEditText(boolean enable){
64 | mAllowEditText = enable;
65 | }
66 |
67 | private TextEditDialog getDialog() {
68 | if (mDialog == null) {
69 | mDialog = new TextEditDialog(getContext(), this);
70 | }
71 | return mDialog;
72 | }
73 |
74 | @Override
75 | public void onText(StickerText text, boolean enableEdit) {
76 | mText = text;
77 | mAllowEditText = enableEdit;
78 | if (mText != null && mTextView != null) {
79 | mTextView.setText(mText.getText());
80 | GradientDrawable myGrad = (GradientDrawable) mTextView.getBackground();
81 | if (mText.isDrawBackground()) {
82 | int color = text.getColor();
83 | boolean isWhite = color == Color.WHITE;
84 | mTextView.setTextColor(isWhite ? Color.BLACK : Color.WHITE);
85 | myGrad.setColor(text.getColor());
86 | setPadding(PADDING_BG, PADDING_BG, PADDING_BG, PADDING_BG);
87 | } else {
88 | mTextView.setTextColor(text.getColor());
89 | myGrad.setColor(Color.TRANSPARENT);
90 | setPadding(0, 0, 0, 0);
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/widget/ColorGroup.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.widget;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.util.AttributeSet;
6 | import android.widget.RadioGroup;
7 |
8 |
9 | public class ColorGroup extends RadioGroup {
10 |
11 | public ColorGroup(Context context) {
12 | super(context);
13 | }
14 |
15 | public ColorGroup(Context context, AttributeSet attrs) {
16 | super(context, attrs);
17 | }
18 |
19 | public int getCheckColor() {
20 | int checkedId = getCheckedRadioButtonId();
21 | ColorRadio radio = findViewById(checkedId);
22 | if (radio != null) {
23 | return radio.getColor();
24 | }
25 | return Color.WHITE;
26 | }
27 |
28 | public void setCheckColor(int color) {
29 | int count = getChildCount();
30 | for (int i = 0; i < count; i++) {
31 | ColorRadio radio = (ColorRadio) getChildAt(i);
32 | if (radio.getColor() == color) {
33 | radio.setChecked(true);
34 | break;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/widget/ColorRadio.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.widget;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.PorterDuff;
10 | import android.graphics.PorterDuffXfermode;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 | import android.view.animation.AccelerateDecelerateInterpolator;
14 | import android.widget.RadioButton;
15 |
16 | import com.vachel.editor.R;
17 |
18 | public class ColorRadio extends RadioButton implements ValueAnimator.AnimatorUpdateListener {
19 | public static final int TYPE_MOSAIC_COLOR = 0; // 这里将透明定义为马赛克
20 | private int mColor = Color.WHITE;
21 | private int mStrokeColor = Color.WHITE;
22 | private float mRadiusRatio = 0f;
23 | private ValueAnimator mAnimator;
24 | private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
25 | private static final float RADIUS_BASE = 0.7f;
26 | private static final float RADIUS_RING = 0.9f;
27 | private static final float RADIUS_BALL = 0.75f;
28 |
29 | public ColorRadio(Context context) {
30 | this(context, null, 0);
31 | }
32 |
33 | public ColorRadio(Context context, AttributeSet attrs) {
34 | super(context, attrs);
35 | initialize(context, attrs, 0);
36 | }
37 |
38 | public ColorRadio(Context context, AttributeSet attrs, int defStyleAttr) {
39 | super(context, attrs, defStyleAttr);
40 | initialize(context, attrs, defStyleAttr);
41 | }
42 |
43 | private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
44 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorRadio);
45 |
46 | mColor = a.getColor(R.styleable.ColorRadio_image_color, Color.WHITE);
47 | mStrokeColor = a.getColor(R.styleable.ColorRadio_image_stroke_color, Color.WHITE);
48 |
49 | a.recycle();
50 |
51 | setButtonDrawable(null);
52 |
53 | mPaint.setColor(mColor);
54 | mPaint.setStrokeWidth(5f);
55 | setLayerType(View.LAYER_TYPE_SOFTWARE, null);
56 | }
57 |
58 | private ValueAnimator getAnimator() {
59 | if (mAnimator == null) {
60 | mAnimator = ValueAnimator.ofFloat(0f, 1f);
61 | mAnimator.addUpdateListener(this);
62 | mAnimator.setDuration(200);
63 | mAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
64 | }
65 | return mAnimator;
66 | }
67 |
68 | public void setColor(int color) {
69 | mColor = color;
70 | mPaint.setColor(mColor);
71 | }
72 |
73 | public int getColor() {
74 | return mColor;
75 | }
76 |
77 | @Override
78 | public void draw(Canvas canvas) {
79 | super.draw(canvas);
80 |
81 | float hw = getWidth() / 2f, hh = getHeight() / 2f;
82 | float radius = Math.min(hw, hh);
83 |
84 | canvas.save();
85 | mPaint.setStyle(Paint.Style.FILL);
86 | if (mColor == TYPE_MOSAIC_COLOR) {
87 | // 绘制马赛克
88 | drawMosaic(canvas, hw, hh, radius);
89 | } else {
90 | mPaint.setStrokeWidth(5);
91 | mPaint.setColor(mColor);
92 | canvas.drawCircle(hw, hh, getBallRadius(radius), mPaint);
93 | }
94 |
95 | mPaint.setColor(mStrokeColor);
96 | mPaint.setStyle(Paint.Style.STROKE);
97 | canvas.drawCircle(hw, hh, getRingRadius(radius), mPaint);
98 | canvas.restore();
99 | }
100 |
101 | private void drawMosaic(Canvas canvas, float hw, float hh, float radius) {
102 | float ringRadius = getRingRadius(radius);
103 | float perWidth = ringRadius * 2 / 5;
104 | mPaint.setStrokeWidth(perWidth);
105 | float startX = hw - ringRadius;
106 | float startY = hh - ringRadius;
107 | float currentX, currentY;
108 | for (int i = 0; i < 5; i++) {
109 | currentY = (i + 0.5f) * perWidth + startY;
110 | for (int j = 0; j < 5; j++) {
111 | currentX = j * perWidth + startX;
112 | mPaint.setColor((i + j) % 2 == 0 ? Color.WHITE : Color.BLACK);
113 | canvas.drawLine(currentX, currentY, currentX + perWidth, currentY, mPaint);
114 | }
115 | }
116 | mPaint.setStyle(Paint.Style.STROKE);
117 | mPaint.setStrokeWidth(hw);
118 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
119 | canvas.drawCircle(hw, hh, ringRadius + hw/2f, mPaint);
120 | // 复原
121 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
122 | mPaint.setStrokeWidth(5);
123 | }
124 |
125 | private float getBallRadius(float radius) {
126 | return radius * ((RADIUS_BALL - RADIUS_BASE) * mRadiusRatio + RADIUS_BASE);
127 | }
128 |
129 | private float getRingRadius(float radius) {
130 | return radius * ((RADIUS_RING - RADIUS_BASE) * mRadiusRatio + RADIUS_BASE);
131 | }
132 |
133 | @Override
134 | public void setChecked(boolean checked) {
135 | boolean isChanged = checked != isChecked();
136 |
137 | super.setChecked(checked);
138 |
139 | if (isChanged) {
140 | ValueAnimator animator = getAnimator();
141 |
142 | if (checked) {
143 | animator.start();
144 | } else {
145 | animator.reverse();
146 | }
147 | }
148 | }
149 |
150 | @Override
151 | public void onAnimationUpdate(ValueAnimator animation) {
152 | mRadiusRatio = (float) animation.getAnimatedValue();
153 | invalidate();
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/widget/ProgressDialog.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.widget;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ProgressBar;
10 | import android.widget.TextView;
11 |
12 | import androidx.annotation.NonNull;
13 | import androidx.lifecycle.Lifecycle;
14 | import androidx.lifecycle.LifecycleObserver;
15 | import androidx.lifecycle.LifecycleOwner;
16 | import androidx.lifecycle.OnLifecycleEvent;
17 |
18 | import com.vachel.editor.R;
19 | import com.vachel.editor.util.Utils;
20 |
21 | public class ProgressDialog extends Dialog implements LifecycleObserver {
22 |
23 | public ProgressDialog(@NonNull Context context) {
24 | super(context, R.style.ProgressDialog);
25 | initDialog(context, "");
26 | }
27 |
28 |
29 | public ProgressDialog(@NonNull Context context, String description) {
30 | super(context, R.style.ProgressDialog);
31 | initDialog(context, description);
32 | }
33 |
34 | public ProgressDialog bindLifeCycle(@NonNull final LifecycleOwner owner){
35 | owner.getLifecycle().addObserver(this);
36 | setOnDismissListener(dialog -> owner.getLifecycle().removeObserver(ProgressDialog.this));
37 | return this;
38 | }
39 |
40 | public void initDialog(Context context, String description) {
41 | OnKeyListener onKeyListener = (arg0, arg1, arg2) -> true;
42 | LayoutInflater mInflater = LayoutInflater.from(context);
43 | View convertView = mInflater.inflate(R.layout.progress_dialog_view, null);
44 | ProgressBar progressBar = convertView.findViewById(R.id.refreshing);
45 | TextView text = convertView.findViewById(R.id.text);
46 | if (!description.equals("")) {
47 | text.setPadding(Utils.dip2px(context,8),0,0,0);
48 | convertView.setBackgroundResource(R.drawable.white_rectangle);
49 | }else{
50 | convertView.setBackground(null);
51 | ViewGroup.LayoutParams params = progressBar.getLayoutParams();
52 | params.height =Utils.dip2px(context,32);
53 | params.width =Utils.dip2px(context,32);
54 | progressBar.setLayoutParams(params);
55 | }
56 | text.setText(description);
57 | setContentView(convertView);
58 | setCancelable(false);
59 | setCanceledOnTouchOutside(false);
60 | setOnKeyListener(onKeyListener);
61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
62 | this.create();
63 | }
64 | }
65 |
66 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
67 | public void onStop() {
68 | dismiss();
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/widget/StickerImageDialog.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.widget;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.os.Bundle;
6 | import android.view.Gravity;
7 | import android.view.View;
8 | import android.view.Window;
9 | import android.view.WindowManager;
10 | import android.widget.LinearLayout;
11 |
12 | import androidx.annotation.NonNull;
13 |
14 | import com.vachel.editor.R;
15 |
16 | public class StickerImageDialog extends Dialog {
17 |
18 | private View mContentView;
19 |
20 | public StickerImageDialog(@NonNull Context context, View contentView) {
21 | this(context);
22 | mContentView = contentView;
23 | }
24 |
25 | public StickerImageDialog(@NonNull Context context) {
26 | this(context, R.style.BottomDialog);
27 | }
28 |
29 | public StickerImageDialog(@NonNull Context context, int themeResId) {
30 | super(context, themeResId);
31 |
32 | }
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.sticker_layout);
38 | Window window = getWindow();
39 | if (window != null) {
40 | LinearLayout.LayoutParams attachLp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 900);
41 | LinearLayout rootView = findViewById(R.id.root_view);
42 | rootView.addView(mContentView, attachLp);
43 | window.setGravity(Gravity.BOTTOM);
44 | window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
45 | }
46 | setCanceledOnTouchOutside(true);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/ui/widget/TextEditDialog.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.ui.widget;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.graphics.Color;
6 | import android.graphics.drawable.GradientDrawable;
7 | import android.os.Bundle;
8 | import android.text.TextUtils;
9 | import android.view.MotionEvent;
10 | import android.view.View;
11 | import android.view.Window;
12 | import android.view.WindowManager.LayoutParams;
13 | import android.view.inputmethod.InputMethodManager;
14 | import android.widget.EditText;
15 | import android.widget.RadioGroup;
16 | import android.widget.TextView;
17 |
18 | import com.vachel.editor.PictureEditor;
19 | import com.vachel.editor.util.Utils;
20 | import com.vachel.editor.R;
21 | import com.vachel.editor.bean.StickerText;
22 |
23 | public class TextEditDialog extends Dialog implements View.OnClickListener,
24 | RadioGroup.OnCheckedChangeListener, View.OnTouchListener {
25 |
26 | private EditText mEditText;
27 |
28 | private final ITextChangedListener mTextListener;
29 |
30 | private StickerText mDefaultText;
31 |
32 | private ColorGroup mColorGroup;
33 | private View mEnableDrawBg;
34 | private int mCurrentColor;
35 |
36 | public TextEditDialog(Context context, ITextChangedListener ITextChangedListener) {
37 | super(context, R.style.TextEditDialog);
38 | setContentView(R.layout.edit_text_dialog);
39 | mTextListener = ITextChangedListener;
40 | Window window = getWindow();
41 | if (window != null) {
42 | window.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
43 | }
44 | }
45 |
46 | @Override
47 | protected void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 |
50 | mColorGroup = findViewById(R.id.cg_colors);
51 | mColorGroup.setOnCheckedChangeListener(this);
52 | mEditText = findViewById(R.id.et_text);
53 | mEnableDrawBg = findViewById(R.id.enable_bg_btn);
54 | mEditText.requestFocus();
55 |
56 | findViewById(R.id.tv_cancel).setOnClickListener(this);
57 | TextView tvDone = findViewById(R.id.tv_done);
58 | tvDone.setOnClickListener(this);
59 | GradientDrawable gd = new GradientDrawable();
60 | gd.setColor(PictureEditor.getInstance().getBtnColor(getContext()));
61 | gd.setCornerRadius(Utils.dip2px(getContext(), 4));
62 | tvDone.setBackground(gd);
63 | mEnableDrawBg.setOnClickListener(this);
64 |
65 | GradientDrawable editGd = new GradientDrawable();
66 | editGd.setColor(Color.TRANSPARENT);
67 | editGd.setCornerRadius(Utils.dip2px(getContext(), 10));
68 | mEditText.setBackground(editGd);
69 |
70 | findViewById(R.id.root_dialog).setOnTouchListener(this);
71 | }
72 |
73 | private void enableDrawBg(boolean enable) {
74 | GradientDrawable myGrad = (GradientDrawable) mEditText.getBackground();
75 | if (enable) {
76 | boolean isWhite = mCurrentColor == Color.WHITE;
77 | mEditText.setTextColor(isWhite ? Color.BLACK : Color.WHITE);
78 | myGrad.setColor(mCurrentColor);
79 | } else {
80 | mEditText.setTextColor(mCurrentColor);
81 | myGrad.setColor(Color.TRANSPARENT);
82 | }
83 | }
84 |
85 | @Override
86 | protected void onStart() {
87 | super.onStart();
88 | if (mDefaultText != null) {
89 | mEditText.setText(mDefaultText.getText());
90 | mCurrentColor = mDefaultText.getColor();
91 | if (!mDefaultText.isEmpty()) {
92 | mEditText.setSelection(mEditText.length());
93 | }
94 | mEnableDrawBg.setSelected(mDefaultText.isDrawBackground());
95 | enableDrawBg(mDefaultText.isDrawBackground());
96 | mDefaultText = null;
97 | } else {
98 | mEditText.setText("");
99 | mCurrentColor = mColorGroup.getCheckColor();
100 | mEnableDrawBg.setSelected(false);
101 | enableDrawBg(false);
102 | showKeyboard();
103 | }
104 | mColorGroup.setCheckColor(mEditText.getCurrentTextColor());
105 | }
106 |
107 | private void showKeyboard() {
108 | InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
109 | if (imm != null) {
110 | mEditText.requestFocus();
111 | imm.showSoftInput(mEditText, 0);
112 | }
113 | }
114 |
115 | public void setText(StickerText text) {
116 | mDefaultText = text;
117 | }
118 |
119 | public void reset() {
120 | setText(new StickerText(null, Color.WHITE));
121 | }
122 |
123 | @Override
124 | public void onClick(View v) {
125 | int vid = v.getId();
126 | if (vid == R.id.tv_done) {
127 | onDone();
128 | } else if (vid == R.id.tv_cancel) {
129 | dismiss();
130 | } else if (vid == R.id.enable_bg_btn) {
131 | boolean enable = !v.isSelected();
132 | v.setSelected(enable);
133 | enableDrawBg(enable);
134 | }
135 | }
136 |
137 | private void onDone() {
138 | String text = mEditText.getText().toString();
139 | if (!TextUtils.isEmpty(text) && mTextListener != null) {
140 | mTextListener.onText(new StickerText(text, mCurrentColor, mEnableDrawBg.isSelected()), true);
141 | }
142 | dismiss();
143 | }
144 |
145 | @Override
146 | public void onCheckedChanged(RadioGroup group, int checkedId) {
147 | mCurrentColor = mColorGroup.getCheckColor();
148 | enableDrawBg(mEnableDrawBg.isSelected());
149 | }
150 |
151 | @Override
152 | public boolean onTouch(View v, MotionEvent event) {
153 | showKeyboard();
154 | return false;
155 | }
156 |
157 | public interface ITextChangedListener {
158 | void onText(StickerText text, boolean enableEdit);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/util/BitmapUtil.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.util;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.BitmapFactory;
8 | import android.graphics.Canvas;
9 | import android.graphics.Matrix;
10 | import android.graphics.PixelFormat;
11 | import android.graphics.drawable.Drawable;
12 | import android.media.ExifInterface;
13 | import android.net.Uri;
14 | import android.os.Environment;
15 | import android.provider.MediaStore;
16 |
17 | import com.vachel.editor.PictureEditor;
18 |
19 | import java.io.File;
20 | import java.io.FileOutputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.io.OutputStream;
24 |
25 | public class BitmapUtil {
26 | public static Bitmap getBitmapFromUri(Context context, Uri imageUri, int reqWidth, int reqHeight) {
27 | if (imageUri == null) {
28 | return null;
29 | }
30 | InputStream inputStream = null;
31 | InputStream exifInterfaceStream = null;
32 | InputStream optionStream = null;
33 | ExifInterface exifInterface = null;
34 | Bitmap scaledBitmap = null;
35 | try {
36 | optionStream = context.getContentResolver().openInputStream(imageUri);
37 | BitmapFactory.Options options = getImageDimensions(optionStream);
38 | optionStream.close();
39 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
40 | exifInterfaceStream = context.getContentResolver().openInputStream(imageUri);
41 | exifInterface = new ExifInterface(exifInterfaceStream);
42 | exifInterfaceStream.close();
43 | }
44 | inputStream = context.getContentResolver().openInputStream(imageUri);
45 | scaledBitmap = decodeSampledBitmapFromStream(inputStream, options, exifInterface, reqWidth, reqHeight, false);
46 | } catch (IOException e) {
47 | e.printStackTrace();
48 | } finally {
49 | Utils.closeAll(inputStream, exifInterfaceStream, optionStream);
50 | }
51 | return scaledBitmap;
52 | }
53 |
54 | public static BitmapFactory.Options getImageDimensions(InputStream inputStream) {
55 | BitmapFactory.Options options = new BitmapFactory.Options();
56 | options.inJustDecodeBounds = true;
57 | FlushedInputStream fis = new FlushedInputStream(inputStream);
58 | BitmapFactory.decodeStream(fis, null, options);
59 | try {
60 | fis.close();
61 | } catch (IOException e) {
62 | e.printStackTrace();
63 | }
64 | return options;
65 | }
66 |
67 |
68 | public static Bitmap decodeSampledBitmapFromStream(InputStream inputStream, BitmapFactory.Options options, ExifInterface exif, int reqWidth, int reqHeight, boolean isForThumbnail) throws IOException {
69 | options.inJustDecodeBounds = true;
70 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
71 | options.inJustDecodeBounds = false;
72 | Bitmap scaledBitmap = BitmapFactory.decodeStream(inputStream, null, options);
73 | if (exif != null && scaledBitmap != null) {//某些情况scaledBitmap为空 避免空异常
74 | int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
75 | Matrix matrix = new Matrix();
76 | if (orientation == 6) {
77 | matrix.postRotate(90);
78 | } else if (orientation == 3) {
79 | matrix.postRotate(180);
80 | } else if (orientation == 8) {
81 | matrix.postRotate(270);
82 | }
83 |
84 | int bitmapHeight = scaledBitmap.getHeight();
85 | int bitmapWidth = scaledBitmap.getWidth();
86 | //如果图片是长图,太长了就截取上部显示,不在全图取缩略图
87 | int picSizeLimit = PictureEditor.getInstance().getPicSizeLimit();
88 | if (isForThumbnail) {
89 | if (scaledBitmap.getHeight() > picSizeLimit) {
90 | bitmapHeight = picSizeLimit;
91 | }
92 | if (scaledBitmap.getWidth() > picSizeLimit) {
93 | bitmapWidth = picSizeLimit;
94 | }
95 | }
96 | scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, true);
97 |
98 | }
99 | return scaledBitmap;
100 | }
101 |
102 | public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
103 | final int height = options.outHeight;
104 | final int width = options.outWidth;
105 | int inSampleSize = 1;
106 | if (height > reqHeight || width > reqWidth) {
107 | final int halfHeight = height / 2;
108 | final int halfWidth = width / 2;
109 | while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
110 | inSampleSize *= 2;
111 | }
112 | }
113 | return inSampleSize;
114 | }
115 |
116 | /**
117 | * 基于文件路径保存图片,需要注意如果文件路径为公共路径,在Android Q之后会因为没有权限导致保存失败
118 | * 应该改用官方推荐的 MediaStore 方式保存
119 | */
120 | public static boolean saveBitmapFile(Bitmap bitmap, String filePath) {
121 | if (bitmap == null) { //
122 | return false;
123 | }
124 | FileOutputStream fos = null;
125 | try {
126 | File file = new File(filePath);
127 | if (!file.exists()) {
128 | file.getParentFile().mkdirs();
129 | file.createNewFile();
130 | }
131 | fos = new FileOutputStream(file);
132 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
133 | fos.flush();
134 | return true;
135 | } catch (IOException e) {
136 | e.printStackTrace();
137 | return false;
138 | } finally {
139 | Utils.closeAll(fos);
140 | }
141 | }
142 |
143 | public static boolean saveBitmapWithAndroidQ(Context context, Bitmap bitmap, String displayName) {
144 | ContentValues contentValues = new ContentValues();
145 | contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, displayName);
146 | String savePath = Environment.DIRECTORY_DCIM + File.separator + PictureEditor.getInstance().getRelativePath() + File.separator;
147 | contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, savePath);
148 | Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
149 | ContentResolver contentResolver = context.getContentResolver();
150 | Uri insertUri = insert(contentResolver, uri, contentValues);
151 | if (insertUri != null) {
152 | try (OutputStream outputStream = contentResolver.openOutputStream(insertUri)) {
153 | if (outputStream != null) {
154 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
155 | return true;
156 | }
157 | } catch (IOException e) {
158 | e.printStackTrace();
159 | }
160 | }
161 | return false;
162 | }
163 |
164 | private static Uri insert(ContentResolver resolver, Uri uri, ContentValues values) {
165 | Uri insertUri = resolver.insert(uri, values);
166 | if (insertUri == null) {
167 | String originFileName = values.getAsString(MediaStore.Images.Media.DISPLAY_NAME);
168 | values.put(MediaStore.Images.Media.DISPLAY_NAME, appendFileNameTimeSuffix(originFileName));
169 | insertUri = resolver.insert(uri, values);
170 | }
171 | return insertUri;
172 | }
173 |
174 | /**
175 | * 给文件名后面添加 (时间戳) 后缀
176 | *
177 | */
178 | private static String appendFileNameTimeSuffix(String originFileName) {
179 | String appendName = "(" + System.currentTimeMillis() + ")";
180 | return appendFileSuffix(originFileName, appendName);
181 | }
182 |
183 | /**
184 | * 给文件名加后缀
185 | *
186 | * 添加规则:
187 | * 1. 如文件名不包含任何 . ,将后缀添加到文件后;
188 | * 2. 如文件名包含. , 但在最前面,将后缀添加到文件头部,不影响文件真实后缀;
189 | */
190 | private static String appendFileSuffix(String originFileName, String suffix) {
191 | String resultFileName;
192 | int i = originFileName.lastIndexOf(".");
193 | if (i == -1) {
194 | resultFileName = originFileName + suffix;
195 | } else if (i == 0) {
196 | resultFileName = suffix + originFileName;
197 | } else {
198 | resultFileName = originFileName.substring(0, i) + suffix + originFileName.substring(i);
199 | }
200 | return resultFileName;
201 | }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/util/EditUtils.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.util;
2 |
3 | import android.graphics.Matrix;
4 | import android.graphics.RectF;
5 |
6 | import com.vachel.editor.PictureEditor;
7 | import com.vachel.editor.bean.EditState;
8 |
9 |
10 | public class EditUtils {
11 | private static final Matrix M = new Matrix();
12 |
13 | private EditUtils() {
14 |
15 | }
16 |
17 | public static void fitFrameCenter(RectF win, RectF frame) {
18 | frame.offset(win.centerX() - frame.centerX(), win.centerY() - frame.centerY());
19 | }
20 |
21 | public static void fitCenter(RectF win, RectF frame) {
22 | fitCenter(win, frame, 0);
23 | }
24 |
25 | public static void fitCenter(RectF win, RectF frame, float padding) {
26 | fitCenter(win, frame, padding, padding, padding, padding + PictureEditor.getInstance().getClipRectMarginBottom());
27 | }
28 |
29 | public static void fitCenter(RectF win, RectF frame, float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) {
30 | if (win.isEmpty() || frame.isEmpty()) {
31 | return;
32 | }
33 |
34 | if (win.width() < paddingLeft + paddingRight) {
35 | paddingLeft = paddingRight = 0;
36 | // 忽略Padding 值
37 | }
38 |
39 | if (win.height() < paddingTop + paddingBottom) {
40 | paddingTop = paddingBottom = 0;
41 | // 忽略Padding 值
42 | }
43 |
44 | float w = win.width() - paddingLeft - paddingRight;
45 | float h = win.height() - paddingTop - paddingBottom;
46 |
47 | float scale = Math.min(w / frame.width(), h / frame.height());
48 |
49 | // 缩放FIT
50 | frame.set(0, 0, frame.width() * scale, frame.height() * scale);
51 |
52 | // 中心对齐
53 | frame.offset(
54 | win.centerX() + (paddingLeft - paddingRight) / 2 - frame.centerX(),
55 | win.centerY() + (paddingTop - paddingBottom) / 2 - frame.centerY()
56 | );
57 | }
58 |
59 | public static EditState fitHoming(RectF win, RectF frame) {
60 | EditState dHoming = new EditState(0, 0, 1, 0);
61 |
62 | if (frame.contains(win)) {
63 | // 不需要Fit
64 | return dHoming;
65 | }
66 |
67 | // 宽高都小于Win,才有必要放大
68 | if (frame.width() < win.width() && frame.height() < win.height()) {
69 | dHoming.scale = Math.min(win.width() / frame.width(), win.height() / frame.height());
70 | }
71 |
72 | RectF rect = new RectF();
73 | M.setScale(dHoming.scale, dHoming.scale, frame.centerX(), frame.centerY());
74 | M.mapRect(rect, frame);
75 |
76 | if (rect.width() < win.width()) {
77 | dHoming.x += win.centerX() - rect.centerX();
78 | } else {
79 | if (rect.left > win.left) {
80 | dHoming.x += win.left - rect.left;
81 | } else if (rect.right < win.right) {
82 | dHoming.x += win.right - rect.right;
83 | }
84 | }
85 |
86 | if (rect.height() < win.height()) {
87 | dHoming.y += win.centerY() - rect.centerY();
88 | } else {
89 | if (rect.top > win.top) {
90 | dHoming.y += win.top - rect.top;
91 | } else if (rect.bottom < win.bottom) {
92 | dHoming.y += win.bottom - rect.bottom;
93 | }
94 | }
95 |
96 | return dHoming;
97 | }
98 |
99 | public static EditState fitHoming(RectF win, RectF frame, float centerX, float centerY) {
100 | EditState dHoming = new EditState(0, 0, 1, 0);
101 |
102 | if (frame.contains(win)) {
103 | // 不需要Fit
104 | return dHoming;
105 | }
106 |
107 | // 宽高都小于Win,才有必要放大
108 | if (frame.width() < win.width() && frame.height() < win.height()) {
109 | dHoming.scale = Math.min(win.width() / frame.width(), win.height() / frame.height());
110 | }
111 |
112 | RectF rect = new RectF();
113 | M.setScale(dHoming.scale, dHoming.scale, centerX, centerY);
114 | M.mapRect(rect, frame);
115 |
116 | if (rect.width() < win.width()) {
117 | dHoming.x += win.centerX() - rect.centerX();
118 | } else {
119 | if (rect.left > win.left) {
120 | dHoming.x += win.left - rect.left;
121 | } else if (rect.right < win.right) {
122 | dHoming.x += win.right - rect.right;
123 | }
124 | }
125 |
126 | if (rect.height() < win.height()) {
127 | dHoming.y += win.centerY() - rect.centerY();
128 | } else {
129 | if (rect.top > win.top) {
130 | dHoming.y += win.top - rect.top;
131 | } else if (rect.bottom < win.bottom) {
132 | dHoming.y += win.bottom - rect.bottom;
133 | }
134 | }
135 |
136 | return dHoming;
137 | }
138 |
139 |
140 | public static EditState fitHoming(RectF win, RectF frame, boolean isJustInner) {
141 | EditState dHoming = new EditState(0, 0, 1, 0);
142 |
143 | if (frame.contains(win) && !isJustInner) {
144 | // 不需要Fit
145 | return dHoming;
146 | }
147 |
148 | // 宽高都小于Win,才有必要放大
149 | if (isJustInner || frame.width() < win.width() && frame.height() < win.height()) {
150 | dHoming.scale = Math.min(win.width() / frame.width(), win.height() / frame.height());
151 | }
152 |
153 | RectF rect = new RectF();
154 | M.setScale(dHoming.scale, dHoming.scale, frame.centerX(), frame.centerY());
155 | M.mapRect(rect, frame);
156 |
157 | if (rect.width() < win.width()) {
158 | dHoming.x += win.centerX() - rect.centerX();
159 | } else {
160 | if (rect.left > win.left) {
161 | dHoming.x += win.left - rect.left;
162 | } else if (rect.right < win.right) {
163 | dHoming.x += win.right - rect.right;
164 | }
165 | }
166 |
167 | if (rect.height() < win.height()) {
168 | dHoming.y += win.centerY() - rect.centerY();
169 | } else {
170 | if (rect.top > win.top) {
171 | dHoming.y += win.top - rect.top;
172 | } else if (rect.bottom < win.bottom) {
173 | dHoming.y += win.bottom - rect.bottom;
174 | }
175 | }
176 | return dHoming;
177 | }
178 |
179 | public static EditState fillHoming(RectF win, RectF frame) {
180 | EditState dHoming = new EditState(0, 0, 1, 0);
181 | if (frame.contains(win)) {
182 | // 不需要Fill
183 | return dHoming;
184 | }
185 |
186 | if (frame.width() < win.width() || frame.height() < win.height()) {
187 | dHoming.scale = Math.max(win.width() / frame.width(), win.height() / frame.height());
188 | }
189 |
190 | RectF rect = new RectF();
191 | M.setScale(dHoming.scale, dHoming.scale, frame.centerX(), frame.centerY());
192 | M.mapRect(rect, frame);
193 |
194 | if (rect.left > win.left) {
195 | dHoming.x += win.left - rect.left;
196 | } else if (rect.right < win.right) {
197 | dHoming.x += win.right - rect.right;
198 | }
199 |
200 | if (rect.top > win.top) {
201 | dHoming.y += win.top - rect.top;
202 | } else if (rect.bottom < win.bottom) {
203 | dHoming.y += win.bottom - rect.bottom;
204 | }
205 |
206 | return dHoming;
207 | }
208 |
209 | public static EditState fillHoming(RectF win, RectF frame, float pivotX, float pivotY) {
210 | EditState dHoming = new EditState(0, 0, 1, 0);
211 | if (frame.contains(win)) {
212 | // 不需要Fill
213 | return dHoming;
214 | }
215 |
216 | if (frame.width() < win.width() || frame.height() < win.height()) {
217 | dHoming.scale = Math.max(win.width() / frame.width(), win.height() / frame.height());
218 | }
219 |
220 | RectF rect = new RectF();
221 | M.setScale(dHoming.scale, dHoming.scale, pivotX, pivotY);
222 | M.mapRect(rect, frame);
223 |
224 | if (rect.left > win.left) {
225 | dHoming.x += win.left - rect.left;
226 | } else if (rect.right < win.right) {
227 | dHoming.x += win.right - rect.right;
228 | }
229 |
230 | if (rect.top > win.top) {
231 | dHoming.y += win.top - rect.top;
232 | } else if (rect.bottom < win.bottom) {
233 | dHoming.y += win.bottom - rect.bottom;
234 | }
235 |
236 | return dHoming;
237 | }
238 |
239 | public static EditState fill(RectF win, RectF frame) {
240 | EditState dHoming = new EditState(0, 0, 1, 0);
241 |
242 | if (win.equals(frame)) {
243 | return dHoming;
244 | }
245 |
246 | // 第一次时缩放到裁剪区域内
247 | dHoming.scale = Math.max(win.width() / frame.width(), win.height() / frame.height());
248 |
249 | RectF rect = new RectF();
250 | M.setScale(dHoming.scale, dHoming.scale, frame.centerX(), frame.centerY());
251 | M.mapRect(rect, frame);
252 |
253 | dHoming.x += win.centerX() - rect.centerX();
254 | dHoming.y += win.centerY() - rect.centerY();
255 |
256 | return dHoming;
257 | }
258 |
259 | public static int inSampleSize(int rawSampleSize) {
260 | int raw = rawSampleSize, ans = 1;
261 | while (raw > 1) {
262 | ans <<= 1;
263 | raw >>= 1;
264 | }
265 |
266 | if (ans != rawSampleSize) {
267 | ans <<= 1;
268 | }
269 |
270 | return ans;
271 | }
272 |
273 | public static void rectFill(RectF win, RectF frame) {
274 | if (win.equals(frame)) {
275 | return;
276 | }
277 |
278 | float scale = Math.max(win.width() / frame.width(), win.height() / frame.height());
279 |
280 | M.setScale(scale, scale, frame.centerX(), frame.centerY());
281 | M.mapRect(frame);
282 |
283 | if (frame.left > win.left) {
284 | frame.left = win.left;
285 | } else if (frame.right < win.right) {
286 | frame.right = win.right;
287 | }
288 |
289 | if (frame.top > win.top) {
290 | frame.top = win.top;
291 | } else if (frame.bottom < win.bottom) {
292 | frame.bottom = win.bottom;
293 | }
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/util/FlushedInputStream.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.util;
2 |
3 | import java.io.FilterInputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 |
7 | /**
8 | * Memory-friendly workaround found from https://code.google.com/p/android/issues/detail?id=6066#c23
9 | * to solve decoding problems in bitmaps from InputStreams that don't skip if no more stream is available.
10 | */
11 | public class FlushedInputStream extends FilterInputStream {
12 | public FlushedInputStream(InputStream inputStream) {
13 | super(inputStream);
14 | }
15 |
16 | @Override
17 | public long skip(long n) throws IOException {
18 | long totalBytesSkipped = 0L;
19 | while (totalBytesSkipped < n) {
20 | long bytesSkipped = in.skip(n - totalBytesSkipped);
21 | if (bytesSkipped == 0L) {
22 | int inByte = read();
23 | if (inByte < 0) {
24 | break;
25 | } else {
26 | bytesSkipped = 1;
27 | }
28 | }
29 | totalBytesSkipped += bytesSkipped;
30 | }
31 | return totalBytesSkipped;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/editor/src/main/java/com/vachel/editor/util/Utils.java:
--------------------------------------------------------------------------------
1 | package com.vachel.editor.util;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Matrix;
8 | import android.media.ExifInterface;
9 | import android.net.Uri;
10 | import android.os.Build;
11 | import android.os.Environment;
12 | import android.util.DisplayMetrics;
13 | import android.view.WindowManager;
14 |
15 | import androidx.annotation.RequiresApi;
16 |
17 | import com.vachel.editor.PictureEditor;
18 |
19 | import java.io.Closeable;
20 | import java.io.File;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.io.OutputStream;
24 | import java.text.SimpleDateFormat;
25 | import java.util.Date;
26 | import java.util.Locale;
27 |
28 | public class Utils {
29 | public static int dip2px(Context context, float dpValue) {
30 | final float scale = context.getResources().getDisplayMetrics().density;
31 | return (int) (dpValue * scale + 0.5f);
32 | }
33 |
34 | public static int getScreenHeight(Context context) {
35 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
36 | DisplayMetrics outMetrics = new DisplayMetrics();
37 | if (wm == null || wm.getDefaultDisplay() == null) {
38 | return 0;
39 | }
40 | wm.getDefaultDisplay().getMetrics(outMetrics);
41 | return outMetrics.heightPixels;
42 | }
43 |
44 | public static int getScreenWidth(Context context) {
45 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
46 | DisplayMetrics outMetrics = new DisplayMetrics();
47 | if (wm == null || wm.getDefaultDisplay() == null) {
48 | return 0;
49 | }
50 | wm.getDefaultDisplay().getMetrics(outMetrics);
51 | return outMetrics.widthPixels;
52 | }
53 |
54 | // 调用的地方记得要关流
55 | public static void copyStream(InputStream in, OutputStream out) throws IOException {
56 | byte[] buffer = new byte[4096];
57 | int read;
58 |
59 | while ((read = in.read(buffer)) != -1) {
60 | out.write(buffer, 0, read);
61 | }
62 | }
63 |
64 | public static void closeAll(Closeable... closeables){
65 | if(closeables == null){
66 | return;
67 | }
68 | for (Closeable closeable : closeables) {
69 | if(closeable!=null){
70 | try {
71 | closeable.close();
72 | } catch (IOException e) {
73 | e.printStackTrace();
74 | }
75 | }
76 | }
77 | }
78 |
79 | public static boolean dismissDialog(Dialog dialog) {
80 | if (dialog != null && dialog.isShowing()) {
81 | dialog.dismiss();
82 | return true;
83 | }
84 | return false;
85 | }
86 |
87 | public static void recycleBitmap(Bitmap bitmap) {
88 | if (bitmap != null && !bitmap.isRecycled()) {
89 | bitmap.recycle();
90 | }
91 | }
92 |
93 | public static String saveBitmap(Context context, Bitmap bitmap) {
94 | boolean saveSuccess;
95 | String path;
96 | String displayName = "image_" + getCurrentFormatTime() + ".jpg";
97 | if (Build.VERSION.SDK_INT >= 29) {
98 | saveSuccess = BitmapUtil.saveBitmapWithAndroidQ(context, bitmap, displayName);
99 | path = getSaveImagePathByName(displayName);
100 | } else {
101 | path = getSaveFilePath(context, displayName);
102 | saveSuccess = BitmapUtil.saveBitmapFile(bitmap, path);
103 | }
104 | return saveSuccess ? path : "";
105 | }
106 |
107 | private static String getCurrentFormatTime() {
108 | return new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
109 | }
110 |
111 | public static String getSaveFilePath(Context context,String fileName) {
112 | File sdCard = Environment.getExternalStorageDirectory();
113 | File outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + PictureEditor.getInstance().getRelativePath());
114 | if (!outputDirectory.exists()) {
115 | outputDirectory.mkdirs();
116 | }
117 | return outputDirectory.getAbsolutePath() + File.separator + fileName;
118 | }
119 |
120 | /**
121 | * 根据图片名称返回保存的图片路径
122 | * @param displayName 图片名称
123 | * @return 保存的图片路径 /DCIM/edit/displayName
124 | */
125 | @RequiresApi(api = Build.VERSION_CODES.Q)
126 | public static String getSaveImagePathByName(String displayName) {
127 | File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
128 | + File.separator + PictureEditor.getInstance().getRelativePath() + File.separator + displayName);
129 | return file.getPath();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/editor/src/main/res/anim/anim_dialog_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |