├── README.md
├── app
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── left
│ │ └── drawingboard
│ │ ├── MainActivity.java
│ │ ├── SketchView.java
│ │ └── fragment
│ │ └── SketchFragment.java
│ └── res
│ ├── drawable-xhdpi
│ ├── bg_popup.9.png
│ ├── brush.png
│ ├── delete.png
│ ├── eraser.png
│ ├── icon.png
│ ├── photo.png
│ ├── redo.png
│ ├── save.png
│ └── undo.png
│ ├── drawable
│ └── circle.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── fragment_sketch.xml
│ ├── popup_sketch_eraser.xml
│ └── popup_sketch_stroke.xml
│ ├── menu
│ └── main.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── images
├── 1.png
├── 2.png
├── 3.png
└── 4.png
└── settings.gradle
/README.md:
--------------------------------------------------------------------------------
1 | ## DrawingBoard
2 |
3 |
4 |
5 |
6 |
7 | |
8 |
9 |
10 | |
11 |
12 |
13 | |
14 |
15 |
16 | |
17 |
18 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "26.0.1"
6 |
7 | defaultConfig {
8 | applicationId "com.left.drawingboard"
9 | minSdkVersion 14
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | compile 'com.android.support:appcompat-v7:26.0.1'
25 | compile 'com.larswerkman:HoloColorPicker:1.5'
26 | compile 'com.jakewharton:butterknife:7.0.1'
27 | compile 'com.afollestad.material-dialogs:core:0.9.4.7'
28 | compile 'com.yancy.imageselector:imageselector:1.3.0'
29 | compile 'com.github.bumptech.glide:glide:3.7.0'
30 | compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/zhangyipeng/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/left/drawingboard/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.left.drawingboard;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v4.app.FragmentTransaction;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 |
10 | import com.left.drawingboard.fragment.SketchFragment;
11 |
12 | public class MainActivity extends AppCompatActivity {
13 | private final static String FRAGMENT_TAG = "SketchFragmentTag";
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 |
20 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
21 |
22 | ft.add(R.id.fl_main, new SketchFragment(),FRAGMENT_TAG).commit();
23 | }
24 |
25 | // 添加右上角的actionbar
26 | @Override
27 | public boolean onCreateOptionsMenu(Menu menu) {
28 | // 这里是调用menu文件夹中的main.xml,在主界面label右上角的三点里显示其他功能
29 | getMenuInflater().inflate(R.menu.main, menu);
30 | return true;
31 | }
32 |
33 | @Override
34 | public boolean onOptionsItemSelected(MenuItem item) {
35 | super.onOptionsItemSelected(item);
36 | SketchFragment f = (SketchFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
37 | return f.onOptionsItemSelected(item);
38 | }
39 |
40 |
41 | @Override
42 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
43 | super.onActivityResult(requestCode, resultCode, data);
44 | SketchFragment f = (SketchFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
45 | f.onActivityResult(requestCode, resultCode, data);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/left/drawingboard/SketchView.java:
--------------------------------------------------------------------------------
1 | package com.left.drawingboard;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.support.v7.widget.AppCompatImageView;
10 | import android.util.AttributeSet;
11 | import android.util.Pair;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 | import android.view.View.OnTouchListener;
15 |
16 | import java.util.ArrayList;
17 |
18 |
19 | public class SketchView extends AppCompatImageView implements OnTouchListener {
20 |
21 | // 画图板默认参数
22 | public static final int STROKE = 0;
23 | public static final int ERASER = 1;
24 | public static final int DEFAULT_STROKE_SIZE = 7;
25 | public static final int DEFAULT_ERASER_SIZE = 50;
26 | private float strokeSize = DEFAULT_STROKE_SIZE;
27 | // 默认初始化画笔颜色
28 | private int strokeColor = Color.BLACK;
29 | private float eraserSize = DEFAULT_ERASER_SIZE;
30 |
31 | private Path m_Path;
32 | private Paint m_Paint;
33 | private float mX, mY;
34 | private int width, height;
35 |
36 | // 保存画图轨迹,用来做撤销和重做
37 | private ArrayList> paths = new ArrayList<>();
38 | private ArrayList> undonePaths = new ArrayList<>();
39 | // 记录选择的图片,方便后续保存操作
40 | private Bitmap bitmap;
41 | // 默认初始化的mode
42 | private int mode = STROKE;
43 |
44 | private OnDrawChangedListener onDrawChangedListener;
45 |
46 | public SketchView(Context context, AttributeSet attr) {
47 | super(context, attr);
48 |
49 | setFocusable(true);
50 | setFocusableInTouchMode(true);
51 | setBackgroundColor(Color.WHITE);
52 |
53 | this.setOnTouchListener(this);
54 | // 初始化paint
55 | m_Paint = new Paint();
56 | m_Paint.setAntiAlias(true);
57 | m_Paint.setDither(true);
58 | m_Paint.setColor(strokeColor);
59 | m_Paint.setStyle(Paint.Style.STROKE);
60 | m_Paint.setStrokeJoin(Paint.Join.ROUND);
61 | m_Paint.setStrokeCap(Paint.Cap.ROUND);
62 | m_Paint.setStrokeWidth(strokeSize);
63 | m_Path = new Path();
64 |
65 | invalidate();
66 | }
67 |
68 | public int getMode() {
69 | return this.mode;
70 | }
71 |
72 | public void setMode(int mode) {
73 | if (mode == STROKE || mode == ERASER)
74 | this.mode = mode;
75 | }
76 |
77 | @Override
78 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
79 | width = MeasureSpec.getSize(widthMeasureSpec);
80 | height = MeasureSpec.getSize(heightMeasureSpec);
81 |
82 | setMeasuredDimension(width, height);
83 | }
84 |
85 | @Override
86 | public boolean onTouch(View arg0, MotionEvent event) {
87 | float x = event.getX();
88 | float y = event.getY();
89 |
90 | switch (event.getAction()) {
91 | case MotionEvent.ACTION_DOWN:
92 | touch_start(x, y);
93 | invalidate();
94 | break;
95 | case MotionEvent.ACTION_MOVE:
96 | touch_move(x, y);
97 | invalidate();
98 | break;
99 | case MotionEvent.ACTION_UP:
100 | touch_up();
101 | invalidate();
102 | break;
103 | }
104 | return true;
105 | }
106 |
107 | @Override
108 | protected void onDraw(Canvas canvas) {
109 | if (bitmap != null) {
110 | canvas.drawBitmap(bitmap, 0, 0, null);
111 | }
112 |
113 | for (Pair p : paths) {
114 | canvas.drawPath(p.first, p.second);
115 | }
116 | onDrawChangedListener.onDrawChanged();
117 | }
118 |
119 | private void touch_start(float x, float y) {
120 | // 清除undone list
121 | undonePaths.clear();
122 |
123 | if (mode == ERASER) {
124 | m_Paint.setColor(Color.WHITE);
125 | m_Paint.setStrokeWidth(eraserSize);
126 | } else {
127 | m_Paint.setColor(strokeColor);
128 | m_Paint.setStrokeWidth(strokeSize);
129 | }
130 | // 复制m_Paint
131 | Paint newPaint = new Paint(m_Paint);
132 | // 避免啥都没有的时候调用橡皮擦在那里乱擦
133 | if (!(paths.size() == 0 && mode == ERASER && bitmap == null)) {
134 | paths.add(new Pair<>(m_Path, newPaint));
135 | }
136 |
137 | m_Path.reset();
138 | m_Path.moveTo(x, y);
139 | mX = x;
140 | mY = y;
141 | }
142 |
143 | private void touch_move(float x, float y) {
144 | m_Path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
145 | mX = x;
146 | mY = y;
147 | }
148 |
149 | private void touch_up() {
150 | m_Path.lineTo(mX, mY);
151 | // 复制m_Paint
152 | Paint newPaint = new Paint(m_Paint);
153 | // 避免啥都没有的时候调用橡皮擦在那里乱擦
154 | if (!(paths.size() == 0 && mode == ERASER && bitmap == null)) {
155 | paths.add(new Pair<>(m_Path, newPaint));
156 | }
157 | // 避免重复画两次
158 | m_Path = new Path();
159 | }
160 |
161 | // 返回画图结果用来保存
162 | public Bitmap getBitmap() {
163 | if (paths.size() == 0)
164 | return null;
165 | if (bitmap == null) {
166 | bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
167 | // 底色设置成白色
168 | bitmap.eraseColor(Color.WHITE);
169 | }
170 | Canvas canvas = new Canvas(bitmap);
171 | for (Pair p : paths) {
172 | canvas.drawPath(p.first, p.second);
173 | }
174 | return bitmap;
175 | }
176 |
177 | public void setBitmap(Bitmap bitmap) {
178 | this.bitmap = bitmap;
179 | }
180 |
181 | // 撤销一笔
182 | public void undo() {
183 | if (paths.size() >= 2) {
184 | undonePaths.add(paths.remove(paths.size() - 1));
185 | // 有两种动作,一种是touch,一种是move,所以需要做两次
186 | undonePaths.add(paths.remove(paths.size() - 1));
187 | invalidate();
188 | }
189 | }
190 |
191 | // 重做一笔
192 | public void redo() {
193 | if (undonePaths.size() > 0) {
194 | paths.add(undonePaths.remove(undonePaths.size() - 1));
195 | paths.add(undonePaths.remove(undonePaths.size() - 1));
196 | invalidate();
197 | }
198 | }
199 |
200 | public int getUndoneCount() {
201 | return undonePaths.size();
202 | }
203 |
204 | public ArrayList> getPaths() {
205 | return paths;
206 | }
207 |
208 | public void setSize(int size, int eraserOrStroke) {
209 | switch (eraserOrStroke) {
210 | case STROKE:
211 | strokeSize = size;
212 | break;
213 | case ERASER:
214 | eraserSize = size;
215 | break;
216 | }
217 | }
218 |
219 | public int getStrokeColor() {
220 | return this.strokeColor;
221 | }
222 |
223 | public void setStrokeColor(int color) {
224 | strokeColor = color;
225 | }
226 |
227 | // 删除所有画图,包括选择的图片
228 | public void erase() {
229 | paths.clear();
230 | undonePaths.clear();
231 | // 先判断是否已经回收
232 | if (bitmap != null && !bitmap.isRecycled()) {
233 | // 回收并且置为null
234 | bitmap.recycle();
235 | bitmap = null;
236 | }
237 | System.gc();
238 | invalidate();
239 | }
240 |
241 | public void setOnDrawChangedListener(OnDrawChangedListener listener) {
242 | this.onDrawChangedListener = listener;
243 | }
244 |
245 | public interface OnDrawChangedListener {
246 | void onDrawChanged();
247 | }
248 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/left/drawingboard/fragment/SketchFragment.java:
--------------------------------------------------------------------------------
1 | package com.left.drawingboard.fragment;
2 |
3 | import android.animation.ObjectAnimator;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.graphics.Bitmap;
8 | import android.graphics.Matrix;
9 | import android.graphics.drawable.BitmapDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.os.AsyncTask;
12 | import android.os.Bundle;
13 | import android.support.annotation.NonNull;
14 | import android.support.v4.app.Fragment;
15 | import android.text.InputType;
16 | import android.util.DisplayMetrics;
17 | import android.view.LayoutInflater;
18 | import android.view.MenuItem;
19 | import android.view.View;
20 | import android.view.View.OnClickListener;
21 | import android.view.ViewGroup;
22 | import android.view.WindowManager;
23 | import android.widget.FrameLayout;
24 | import android.widget.ImageView;
25 | import android.widget.LinearLayout.LayoutParams;
26 | import android.widget.PopupWindow;
27 | import android.widget.SeekBar;
28 | import android.widget.SeekBar.OnSeekBarChangeListener;
29 | import android.widget.Toast;
30 |
31 | import com.afollestad.materialdialogs.MaterialDialog;
32 | import com.bumptech.glide.Glide;
33 | import com.bumptech.glide.request.animation.GlideAnimation;
34 | import com.bumptech.glide.request.target.SimpleTarget;
35 | import com.larswerkman.holocolorpicker.ColorPicker;
36 | import com.larswerkman.holocolorpicker.OpacityBar;
37 | import com.larswerkman.holocolorpicker.SVBar;
38 | import com.left.drawingboard.R;
39 | import com.left.drawingboard.SketchView;
40 | import com.yancy.imageselector.ImageConfig;
41 | import com.yancy.imageselector.ImageLoader;
42 | import com.yancy.imageselector.ImageSelector;
43 | import com.yancy.imageselector.ImageSelectorActivity;
44 |
45 | import java.io.File;
46 | import java.io.FileOutputStream;
47 | import java.util.List;
48 |
49 | import butterknife.Bind;
50 | import butterknife.ButterKnife;
51 | import jp.co.cyberagent.android.gpuimage.GPUImage;
52 | import jp.co.cyberagent.android.gpuimage.GPUImageSketchFilter;
53 |
54 |
55 | public class SketchFragment extends Fragment implements SketchView.OnDrawChangedListener {
56 |
57 | @Bind(R.id.sketch_stroke)
58 | ImageView stroke;
59 | @Bind(R.id.sketch_eraser)
60 | ImageView eraser;
61 | @Bind(R.id.drawing)
62 | SketchView mSketchView;
63 | @Bind(R.id.sketch_undo)
64 | ImageView undo;
65 | @Bind(R.id.sketch_redo)
66 | ImageView redo;
67 | @Bind(R.id.sketch_erase)
68 | ImageView erase;
69 | @Bind(R.id.sketch_save)
70 | ImageView sketchSave;
71 | @Bind(R.id.sketch_photo)
72 | ImageView sketchPhoto;
73 | @Bind(R.id.iv_painted)
74 | ImageView ivPainted;
75 | @Bind(R.id.iv_original)
76 | ImageView ivOriginal;
77 |
78 | private int seekBarStrokeProgress, seekBarEraserProgress;
79 | private View popupStrokeLayout, popupEraserLayout;
80 | private ImageView strokeImageView, eraserImageView;
81 | // 调色板中黑色小圆球的size
82 | private int size;
83 | private ColorPicker mColorPicker;
84 | // 记录弹出调色板中ColorPicker的颜色,用以弹出时的颜色中心初始化
85 | private int oldColor;
86 | private MaterialDialog dialog;
87 | private Bitmap bitmap;
88 | private Bitmap dstBmp;
89 | private Bitmap grayBmp;
90 | private int mScreenWidth;
91 | private int mScreenHeight;
92 |
93 |
94 | @Override
95 | public void onCreate(Bundle savedInstanceState) {
96 | super.onCreate(savedInstanceState);
97 | setHasOptionsMenu(true);
98 | setRetainInstance(false);
99 | }
100 |
101 | @Override
102 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
103 | View view = inflater.inflate(R.layout.fragment_sketch, container, false);
104 | ButterKnife.bind(this, view);
105 |
106 | DisplayMetrics dm = new DisplayMetrics();
107 | getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
108 | // 获取屏幕分辨率宽度
109 | mScreenWidth = dm.widthPixels;
110 | mScreenHeight = dm.heightPixels;
111 | // 给mSketchView设置宽高
112 | FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mSketchView.getLayoutParams();
113 | p.width = mScreenWidth;
114 | p.height = mScreenHeight;
115 | mSketchView.setLayoutParams(p);
116 |
117 | return view;
118 | }
119 |
120 | @Override
121 | public void onActivityCreated(Bundle savedInstanceState) {
122 | super.onActivityCreated(savedInstanceState);
123 |
124 | mSketchView.setOnDrawChangedListener(this);
125 |
126 | stroke.setOnClickListener(new OnClickListener() {
127 | @Override
128 | public void onClick(View v) {
129 |
130 | if (mSketchView.getMode() == SketchView.STROKE) {
131 | showPopup(v, SketchView.STROKE);
132 | } else {
133 | mSketchView.setMode(SketchView.STROKE);
134 | setAlpha(eraser, 0.4f);
135 | setAlpha(stroke, 1f);
136 | }
137 | }
138 | });
139 | // 默认初始化时橡皮擦为未选中状态
140 | setAlpha(eraser, 0.4f);
141 | eraser.setOnClickListener(new OnClickListener() {
142 | @Override
143 | public void onClick(View v) {
144 | if (mSketchView.getMode() == SketchView.ERASER) {
145 | showPopup(v, SketchView.ERASER);
146 | } else {
147 | mSketchView.setMode(SketchView.ERASER);
148 | setAlpha(stroke, 0.4f);
149 | setAlpha(eraser, 1f);
150 | }
151 | }
152 | });
153 |
154 | undo.setOnClickListener(new OnClickListener() {
155 | @Override
156 | public void onClick(View v) {
157 | mSketchView.undo();
158 | }
159 | });
160 |
161 | redo.setOnClickListener(new OnClickListener() {
162 | @Override
163 | public void onClick(View v) {
164 | mSketchView.redo();
165 | }
166 | });
167 | // 擦除画图
168 | erase.setOnClickListener(new OnClickListener() {
169 | @Override
170 | public void onClick(View v) {
171 | new MaterialDialog.Builder(getActivity()).content("擦除所有画图?").positiveText("确认")
172 | .negativeText("取消").callback(new MaterialDialog.ButtonCallback() {
173 | @Override
174 | public void onPositive(MaterialDialog dialog) {
175 | mSketchView.erase();
176 | // 记得把ivPainted、ivOriginal置空
177 | ivPainted.setImageBitmap(null);
178 | ivOriginal.setImageBitmap(null);
179 | }
180 | }).build().show();
181 | }
182 | });
183 | // 保存画图
184 | sketchSave.setOnClickListener(new OnClickListener() {
185 | @Override
186 | public void onClick(View v) {
187 | if (mSketchView.getPaths().size() == 0) {
188 | Toast.makeText(getActivity(), "你还没有画图", Toast.LENGTH_SHORT).show();
189 | return;
190 | }
191 | //保存
192 | new MaterialDialog.Builder(getActivity()).title("保存").negativeText("取消").inputType(InputType
193 | .TYPE_CLASS_TEXT).input("画图名称(.png)", "a.png", new MaterialDialog.InputCallback() {
194 | @Override
195 | public void onInput(@NonNull MaterialDialog dialog, CharSequence input) {
196 | if (input == null || input.length() == 0) {
197 | Toast.makeText(getActivity(), "请输入画图名称", Toast.LENGTH_SHORT).show();
198 | } else if (input.length() <= 4 || !input.subSequence(input.length() - 4, input.length()).toString().equals(".png")) {
199 | Toast.makeText(getActivity(), "请输入正确的画图名称(.png)", Toast.LENGTH_SHORT).show();
200 | } else
201 | save(input.toString());
202 | }
203 | }).show();
204 | }
205 | });
206 | sketchPhoto.setOnClickListener(new OnClickListener() {
207 | @Override
208 | public void onClick(View v) {
209 | //选择图片
210 | ImageConfig imageConfig = new ImageConfig.Builder(new ImageLoader() {
211 | @Override
212 | public void displayImage(Context context, String path, ImageView imageView) {
213 | Glide.with(context).load(path).placeholder(com.yancy.imageselector.R.mipmap.imageselector_photo).centerCrop().into(imageView);
214 | }
215 | }).steepToolBarColor(getResources().getColor(R.color.colorPrimary)).titleBgColor(getResources().getColor(R.color.colorPrimary))
216 | .titleSubmitTextColor(getResources().getColor(R.color.white)).titleTextColor(getResources().getColor(R.color.white))
217 | // 开启单选 (默认为多选)
218 | .singleSelect()
219 | // 开启拍照功能 (默认关闭)
220 | .showCamera()
221 | // 拍照后存放的图片路径(默认 /temp/picture) (会自动创建)
222 | .filePath("/DrawingBoard/Pictures")
223 | .build();
224 | // 开启图片选择器
225 | ImageSelector.open(getActivity(), imageConfig);
226 | }
227 | });
228 |
229 | // Inflate布局文件
230 | LayoutInflater inflaterStroke = (LayoutInflater) getActivity().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
231 | popupStrokeLayout = inflaterStroke.inflate(R.layout.popup_sketch_stroke, null);
232 | LayoutInflater inflaterEraser = (LayoutInflater) getActivity().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
233 | popupEraserLayout = inflaterEraser.inflate(R.layout.popup_sketch_eraser, null);
234 |
235 | // 实例化stroke、eraser弹出调色板黑色小圆球控件
236 | strokeImageView = popupStrokeLayout.findViewById(R.id.stroke_circle);
237 | eraserImageView = popupEraserLayout.findViewById(R.id.stroke_circle);
238 |
239 | final Drawable circleDrawable = getResources().getDrawable(R.drawable.circle);
240 | size = circleDrawable.getIntrinsicWidth();
241 |
242 | setSeekBarProgress(SketchView.DEFAULT_STROKE_SIZE, SketchView.STROKE);
243 | setSeekBarProgress(SketchView.DEFAULT_ERASER_SIZE, SketchView.ERASER);
244 |
245 | // stroke color picker初始化
246 | mColorPicker = popupStrokeLayout.findViewById(R.id.stroke_color_picker);
247 | mColorPicker.addSVBar((SVBar) popupStrokeLayout.findViewById(R.id.sv_bar));
248 | mColorPicker.addOpacityBar((OpacityBar) popupStrokeLayout.findViewById(R.id.opacity_bar));
249 |
250 | mColorPicker.setOnColorChangedListener(new ColorPicker.OnColorChangedListener() {
251 | @Override
252 | public void onColorChanged(int color) {
253 | mSketchView.setStrokeColor(color);
254 | }
255 | });
256 | mColorPicker.setColor(mSketchView.getStrokeColor());
257 | mColorPicker.setOldCenterColor(mSketchView.getStrokeColor());
258 | }
259 |
260 | void setAlpha(View v, float alpha) {
261 | v.setAlpha(alpha);
262 | }
263 |
264 | public void save(final String imgName) {
265 | dialog = new MaterialDialog.Builder(getActivity()).title("保存画图").content("保存中...").progress(true, 0).progressIndeterminateStyle(true).show();
266 | bitmap = mSketchView.getBitmap();
267 |
268 | new AsyncTask() {
269 |
270 | @Override
271 | protected Object doInBackground(Object[] params) {
272 |
273 | if (bitmap != null) {
274 | try {
275 | String filePath = "/mnt/sdcard/DrawingBoard/";
276 | File dir = new File(filePath);
277 | if (!dir.exists()) {
278 | dir.mkdirs();
279 | }
280 | File f = new File(filePath, imgName);
281 | if (!f.exists()) {
282 | f.createNewFile();
283 | }
284 | FileOutputStream out = new FileOutputStream(f);
285 | bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
286 | out.close();
287 |
288 | dialog.dismiss();
289 | return "保存画图成功" + filePath + imgName;
290 | } catch (Exception e) {
291 |
292 | dialog.dismiss();
293 | return "保存画图失败" + e.getMessage();
294 | }
295 | }
296 | return null;
297 | }
298 |
299 | @Override
300 | protected void onPostExecute(Object o) {
301 | super.onPostExecute(o);
302 | Toast.makeText(getActivity(), (String) o, Toast.LENGTH_SHORT).show();
303 |
304 | }
305 | }.execute("");
306 | }
307 |
308 | // 显示弹出调色板
309 | private void showPopup(View anchor, final int eraserOrStroke) {
310 |
311 | boolean isErasing = eraserOrStroke == SketchView.ERASER;
312 |
313 | oldColor = mColorPicker.getColor();
314 |
315 | DisplayMetrics metrics = new DisplayMetrics();
316 | getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
317 |
318 | // 创建弹出调色板
319 | PopupWindow popup = new PopupWindow(getActivity());
320 | popup.setContentView(isErasing ? popupEraserLayout : popupStrokeLayout);
321 | popup.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
322 | popup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
323 | popup.setFocusable(true);
324 | popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
325 | @Override
326 | public void onDismiss() {
327 | if (mColorPicker.getColor() != oldColor)
328 | mColorPicker.setOldCenterColor(oldColor);
329 | }
330 | });
331 |
332 | // 清除默认的半透明背景
333 | popup.setBackgroundDrawable(new BitmapDrawable());
334 |
335 | popup.showAsDropDown(anchor);
336 |
337 | // seekbar初始化
338 | SeekBar mSeekBar;
339 | mSeekBar = (SeekBar) (isErasing ? popupEraserLayout
340 | .findViewById(R.id.stroke_seekbar) : popupStrokeLayout
341 | .findViewById(R.id.stroke_seekbar));
342 | mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
343 | @Override
344 | public void onStopTrackingTouch(SeekBar seekBar) {
345 | }
346 |
347 | @Override
348 | public void onStartTrackingTouch(SeekBar seekBar) {
349 | }
350 |
351 | @Override
352 | public void onProgressChanged(SeekBar seekBar, int progress,
353 | boolean fromUser) {
354 | setSeekBarProgress(progress, eraserOrStroke);
355 | }
356 | });
357 | int progress = isErasing ? seekBarEraserProgress : seekBarStrokeProgress;
358 | mSeekBar.setProgress(progress);
359 | }
360 |
361 | protected void setSeekBarProgress(int progress, int eraserOrStroke) {
362 | int calcProgress = progress > 1 ? progress : 1;
363 |
364 | int newSize = Math.round((size / 100f) * calcProgress);
365 | int offset = Math.round((size - newSize) / 2);
366 |
367 | LayoutParams lp = new LayoutParams(newSize, newSize);
368 | lp.setMargins(offset, offset, offset, offset);
369 | if (eraserOrStroke == SketchView.STROKE) {
370 | strokeImageView.setLayoutParams(lp);
371 | seekBarStrokeProgress = progress;
372 | } else {
373 | eraserImageView.setLayoutParams(lp);
374 | seekBarEraserProgress = progress;
375 | }
376 | mSketchView.setSize(newSize, eraserOrStroke);
377 | }
378 |
379 | // 设置redo、undo的显示状态
380 | @Override
381 | public void onDrawChanged() {
382 | // Undo
383 | if (mSketchView.getPaths().size() > 0)
384 | setAlpha(undo, 1f);
385 | else
386 | setAlpha(undo, 0.4f);
387 | // Redo
388 | if (mSketchView.getUndoneCount() > 0)
389 | setAlpha(redo, 1f);
390 | else
391 | setAlpha(redo, 0.4f);
392 | }
393 |
394 | @Override
395 | public void onDestroyView() {
396 | super.onDestroyView();
397 | ButterKnife.unbind(this);
398 | }
399 |
400 | @Override
401 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
402 |
403 | if (requestCode == ImageSelector.IMAGE_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null) {
404 | // 获取Image Path List
405 | List pathList = data.getStringArrayListExtra(ImageSelectorActivity.EXTRA_RESULT);
406 | for (String path : pathList) {
407 | Glide.with(this).load(path).asBitmap().centerCrop().into(new SimpleTarget() {
408 |
409 | @Override
410 | public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
411 | initBitmap(resource);
412 | }
413 | });
414 | }
415 | }
416 | }
417 |
418 | // 选择图片后初始化图片相关控件
419 | private void initBitmap(Bitmap bitmap) {
420 |
421 | float scaleRatio = 1;
422 |
423 | int width = bitmap.getWidth();
424 | int height = bitmap.getHeight();
425 |
426 | float screenRatio = 1.0f;
427 | float imgRatio = (float) height / (float) width;
428 | if (imgRatio >= screenRatio) {
429 | // 高度大于屏幕,以高为准
430 | scaleRatio = (float) mScreenHeight / (float) height;
431 | }
432 |
433 | if (imgRatio < screenRatio) {
434 | scaleRatio = (float) mScreenWidth / (float) width;
435 | }
436 |
437 | Matrix matrix = new Matrix();
438 | matrix.postScale(scaleRatio, scaleRatio);
439 | dstBmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
440 |
441 | GPUImage gpuImage = new GPUImage(getActivity());
442 | // 这是手绘效果的filter
443 | gpuImage.setFilter(new GPUImageSketchFilter());
444 | grayBmp = gpuImage.getBitmapWithFilterApplied(dstBmp);
445 |
446 | // 设置下透明度,不然原图会看不见
447 | mSketchView.getBackground().setAlpha(150);
448 | ivPainted.setImageBitmap(grayBmp);
449 | ivOriginal.setImageBitmap(dstBmp);
450 | // 默认初始时显示手绘效果
451 | ObjectAnimator alpha = ObjectAnimator.ofFloat(ivOriginal, "alpha", 1.0f, 0.0f);
452 | alpha.setDuration(2000).start();
453 | // 默认bitmap为grayBmp
454 | mSketchView.setBitmap(grayBmp);
455 | }
456 |
457 | // 切换显示手绘图和原图
458 | @Override
459 | public boolean onOptionsItemSelected(MenuItem item) {
460 | int id = item.getItemId();
461 | switch (id) {
462 | case R.id.show_original:
463 | ObjectAnimator alpha = ObjectAnimator.ofFloat(ivOriginal, "alpha", 0.0f, 1.0f);
464 | alpha.setDuration(1000).start();
465 | mSketchView.setBitmap(dstBmp);
466 | return true;
467 | case R.id.show_painted:
468 | ObjectAnimator alpha2 = ObjectAnimator.ofFloat(ivOriginal, "alpha", 1.0f, 0.0f);
469 | alpha2.setDuration(1000).start();
470 | mSketchView.setBitmap(grayBmp);
471 | return true;
472 | }
473 | return true;
474 | }
475 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/bg_popup.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/bg_popup.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/brush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/brush.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/eraser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/eraser.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/photo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/redo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/redo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/app/src/main/res/drawable-xhdpi/undo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_sketch.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
18 |
19 |
28 |
29 |
38 |
39 |
48 |
49 |
58 |
59 |
60 |
69 |
70 |
79 |
80 |
89 |
90 |
91 |
92 |
95 |
96 |
100 |
101 |
105 |
106 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/popup_sketch_eraser.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
22 |
23 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/popup_sketch_stroke.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
19 |
20 |
25 |
26 |
30 |
31 |
32 |
39 |
40 |
45 |
46 |
51 |
52 |
57 |
58 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 画图板
4 | 画笔大小
5 | 饱和度
6 | 不透明度
7 | 原图
8 | 手绘
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 |
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.3.3'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | maven { url "https://maven.google.com" }
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Sep 18 00:24:49 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/images/1.png
--------------------------------------------------------------------------------
/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/images/2.png
--------------------------------------------------------------------------------
/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/images/3.png
--------------------------------------------------------------------------------
/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leftthomas/DrawingBoard/d6d35c31b59333d270a7913d18292c6f86726fda/images/4.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------