├── 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 | 8 | 11 | 14 | 17 | 18 |
6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 |
-------------------------------------------------------------------------------- /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 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------