├── .gitignore ├── .idea ├── checkstyle-idea.xml ├── encodings.xml ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── universal-image-loader-1.9.5.jar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zyp │ │ └── draw │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zyp │ │ │ └── draw │ │ │ ├── ImageActivity.java │ │ │ ├── MainActivity.java │ │ │ └── view │ │ │ ├── DrawPathData.java │ │ │ ├── DrawingBoardView.java │ │ │ ├── MoveRegionView.java │ │ │ ├── PreviewRegionView.java │ │ │ └── TouchColorPickView.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bg_popup.9.png │ │ ├── circle.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_image.xml │ │ ├── activity_main.xml │ │ ├── popup_sketch_eraser.xml │ │ └── popup_sketch_stroke.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── bg_popup.9.png │ │ ├── ic_action_draw.png │ │ ├── ic_brush.png │ │ ├── ic_delete.png │ │ ├── ic_eraser.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_photo_dark.png │ │ ├── ic_picture.png │ │ ├── ic_redo.png │ │ └── ic_undo.png │ │ ├── mipmap-xxhdpi │ │ ├── app_icon.png │ │ ├── bg.png │ │ ├── direction.png │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zyp │ └── draw │ └── ExampleUnitTest.java ├── art ├── app_icon.png ├── boy.gif ├── boy_color.png └── boy_sketch.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 32 | 33 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 1.8 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Drawingboard 3 | 4 | -------- 5 | 6 | ![app](./art/app_icon.png) 7 | 8 | ![boy_gif](./art/boy.gif) 9 | 10 | ![boy_color](./art/boy_color.png) 11 | 12 | ![boy_sketch](./art/boy_sketch.png) 13 | 14 | ## 功能 15 | 16 | - 素描 17 | - 图片取色 18 | - 画布缩放 19 | - 预览 20 | 21 | ## 致谢 22 | 23 | Drawingboard的预览及缩放功能借鉴的开源项目 24 | 25 | [DrawView](https://github.com/ByoxCode/DrawView) 26 | 27 | ## 开源协议 28 | 29 | [Apache-2.0](https://opensource.org/licenses/Apache-2.0) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.zyp.draw" 7 | minSdkVersion 14 8 | targetSdkVersion 22 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:23.2.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | // implementation 'com.github.Almeros:android-gesture-detectors:v1.0' 29 | implementation 'com.android.support:cardview-v7:27.1.1' 30 | 31 | implementation 'com.larswerkman:HoloColorPicker:1.5' 32 | 33 | implementation('com.github.afollestad.material-dialogs:commons:0.8.5.3@aar') { 34 | transitive = true 35 | } 36 | implementation 'com.yancy.imageselector:imageselector:1.3.0' 37 | implementation 'com.github.bumptech.glide:glide:3.6.1' 38 | implementation 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0' 39 | implementation files('libs/universal-image-loader-1.9.5.jar') 40 | implementation 'com.commit451:PhotoView:1.2.4' 41 | 42 | implementation('com.jakewharton:butterknife:8.5.1') { 43 | exclude module: 'support-compat' //exclude module: 'support-annotations' 44 | } 45 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' 46 | 47 | // implementation 'com.github.chrisbanes:PhotoView:+' 48 | 49 | compile 'com.github.Almeros:android-gesture-detectors:v1.0' 50 | } 51 | -------------------------------------------------------------------------------- /app/libs/universal-image-loader-1.9.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzyyppqq/DrawingBoard/51b7fea16e40e0223e6dac56a771b950d87e8a02/app/libs/universal-image-loader-1.9.5.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zyp/draw/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zyp.draw", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/ImageActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.os.AsyncTask; 8 | import android.os.Bundle; 9 | import android.os.Environment; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.text.InputType; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.widget.Toast; 15 | 16 | import com.afollestad.materialdialogs.MaterialDialog; 17 | 18 | import java.io.File; 19 | import java.io.FileOutputStream; 20 | 21 | import uk.co.senab.photoview.PhotoView; 22 | /** 23 | * Created by zhangyiipeng on 2018/7/6. 24 | */ 25 | 26 | public class ImageActivity extends AppCompatActivity { 27 | 28 | private Bitmap drawBitmap; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_image); 34 | 35 | drawBitmap = drawBg4Bitmap(Color.WHITE, MainActivity.bitmap); 36 | PhotoView photoView = findViewById(R.id.photo_view); 37 | photoView.setImageBitmap(drawBitmap); 38 | findViewById(R.id.bt_save).setOnClickListener(new View.OnClickListener() { 39 | @Override 40 | public void onClick(View v) { 41 | //保存 42 | alertDialog(); 43 | 44 | } 45 | }); 46 | 47 | } 48 | 49 | private void alertDialog() { 50 | new MaterialDialog.Builder(ImageActivity.this) 51 | .title("保存") 52 | .content("") 53 | .inputType(InputType.TYPE_CLASS_TEXT) 54 | .input("手绘名称(.png)", "DrawingBoard.png", new MaterialDialog.InputCallback() { 55 | @Override 56 | public void onInput(MaterialDialog dialog, CharSequence input) { 57 | // Do something 58 | String drawName = input.toString(); 59 | Log.d("AAA", drawName); 60 | 61 | saveDrawBimap(drawBitmap, drawName); 62 | } 63 | }).show(); 64 | } 65 | 66 | public void saveDrawBimap(final Bitmap bitmap, final String imgName) { 67 | final MaterialDialog dialog = new MaterialDialog.Builder(this) 68 | .title("保存手绘") 69 | .content("保存中...") 70 | .progress(true, 0) 71 | .progressIndeterminateStyle(true) 72 | .show(); 73 | 74 | 75 | new AsyncTask() { 76 | @Override 77 | protected Object doInBackground(Object[] params) { 78 | if (bitmap != null) { 79 | try { 80 | String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DrawingBoard/"; 81 | File dir = new File(filePath); 82 | if (!dir.exists()) { 83 | dir.mkdirs(); 84 | } 85 | File f = new File(filePath, imgName); 86 | if (!f.exists()) { 87 | f.createNewFile(); 88 | } 89 | FileOutputStream out = new FileOutputStream(f); 90 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 91 | out.close(); 92 | 93 | dialog.dismiss(); 94 | return "保存手绘成功" + filePath; 95 | } catch (Exception e) { 96 | 97 | dialog.dismiss(); 98 | Log.i("AAA", e.getMessage()); 99 | return "保存手绘失败" + e.getMessage(); 100 | } 101 | } 102 | 103 | return null; 104 | } 105 | 106 | @Override 107 | protected void onPostExecute(Object o) { 108 | super.onPostExecute(o); 109 | Toast.makeText(ImageActivity.this, (String) o, Toast.LENGTH_SHORT).show(); 110 | 111 | } 112 | }.execute(""); 113 | } 114 | 115 | 116 | public Bitmap drawBg4Bitmap(int color, Bitmap orginBitmap) { 117 | Paint paint = new Paint(); 118 | paint.setColor(color); 119 | Bitmap bitmap = Bitmap.createBitmap(orginBitmap.getWidth(), 120 | orginBitmap.getHeight(), orginBitmap.getConfig()); 121 | Canvas canvas = new Canvas(bitmap); 122 | canvas.drawRect(0, 0, orginBitmap.getWidth(), orginBitmap.getHeight(), paint); 123 | canvas.drawBitmap(orginBitmap, 0, 0, paint); 124 | return bitmap; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Color; 8 | import android.graphics.drawable.BitmapDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.os.Bundle; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.util.DisplayMetrics; 13 | import android.util.Log; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.WindowManager; 17 | import android.widget.Button; 18 | import android.widget.ImageView; 19 | import android.widget.LinearLayout; 20 | import android.widget.PopupWindow; 21 | import android.widget.SeekBar; 22 | import android.widget.Toast; 23 | 24 | import com.afollestad.materialdialogs.MaterialDialog; 25 | import com.bumptech.glide.Glide; 26 | import com.bumptech.glide.request.animation.GlideAnimation; 27 | import com.bumptech.glide.request.target.SimpleTarget; 28 | import com.larswerkman.holocolorpicker.ColorPicker; 29 | import com.larswerkman.holocolorpicker.OpacityBar; 30 | import com.larswerkman.holocolorpicker.SVBar; 31 | import com.yancy.imageselector.ImageConfig; 32 | import com.yancy.imageselector.ImageLoader; 33 | import com.yancy.imageselector.ImageSelector; 34 | import com.yancy.imageselector.ImageSelectorActivity; 35 | import com.zyp.draw.view.DrawingBoardView; 36 | import com.zyp.draw.view.MoveRegionView; 37 | 38 | import java.util.List; 39 | 40 | import butterknife.BindView; 41 | import butterknife.ButterKnife; 42 | import butterknife.OnClick; 43 | /** 44 | * Created by zhangyiipeng on 2018/7/6. 45 | */ 46 | 47 | public class MainActivity extends AppCompatActivity { 48 | 49 | @BindView(R.id.drawingBoardView) 50 | DrawingBoardView drawingBoardView; 51 | @BindView(R.id.bt_draw_sketch) 52 | Button btDrawSketch; 53 | @BindView(R.id.bt_draw_colours) 54 | Button btDrawColours; 55 | @BindView(R.id.bt_zoom_up) 56 | Button btZoomUp; 57 | @BindView(R.id.bt_zoom_down) 58 | Button btZoomDown; 59 | @BindView(R.id.bt_save) 60 | Button btSave; 61 | @BindView(R.id.iv_touch) 62 | MoveRegionView ivTouch; 63 | @BindView(R.id.iv_image_selector) 64 | ImageView ivImageSelector; 65 | @BindView(R.id.iv_delete) 66 | ImageView ivDelete; 67 | @BindView(R.id.iv_eraser) 68 | ImageView ivEraser; 69 | @BindView(R.id.iv_brush) 70 | ImageView ivBrush; 71 | @BindView(R.id.view_color) 72 | View viewColor; 73 | @BindView(R.id.iv_undo) 74 | ImageView ivUndo; 75 | @BindView(R.id.iv_redo) 76 | ImageView ivRedo; 77 | @BindView(R.id.view_popu) 78 | View viewPopu; 79 | 80 | public static Bitmap bitmap; 81 | 82 | @Override 83 | protected void onDestroy() { 84 | super.onDestroy(); 85 | bitmap = null; 86 | } 87 | 88 | @Override 89 | protected void onCreate(Bundle savedInstanceState) { 90 | super.onCreate(savedInstanceState); 91 | setContentView(R.layout.activity_main); 92 | ButterKnife.bind(this); 93 | 94 | ivTouch.setTouchMoveListener(new MoveRegionView.OnTouchMoveListener() { 95 | @Override 96 | public void onTouchMove(float mx, float my,boolean isMoving) { 97 | drawingBoardView.moveCanvas(mx, my,isMoving); 98 | 99 | } 100 | }); 101 | drawingBoardView.setOnUndoRedoListener(new DrawingBoardView.OnUndoRedoListener() { 102 | @Override 103 | public void onUndoRedo(boolean isUndo, boolean isRedo) { 104 | Log.d("UNDO", "isUndo:" + isUndo + ",isRedo:" + isRedo); 105 | if (isUndo) { 106 | if (ivUndo.getAlpha() < 1f) { 107 | ivUndo.setAlpha(1f); 108 | } 109 | } else { 110 | if (ivUndo.getAlpha() == 1f) { 111 | ivUndo.setAlpha(0.5f); 112 | } 113 | } 114 | if (isRedo) { 115 | if (ivRedo.getAlpha() < 1f) { 116 | ivRedo.setAlpha(1f); 117 | } 118 | } else { 119 | if (ivRedo.getAlpha() == 1f) { 120 | ivRedo.setAlpha(0.5f); 121 | } 122 | } 123 | } 124 | }); 125 | 126 | btZoomUp.setOnLongClickListener(new View.OnLongClickListener() { 127 | @Override 128 | public boolean onLongClick(View v) { 129 | drawingBoardView.zoomUpQuickCanvas(); 130 | return true; 131 | } 132 | }); 133 | btZoomDown.setOnLongClickListener(new View.OnLongClickListener() { 134 | @Override 135 | public boolean onLongClick(View v) { 136 | drawingBoardView.zoomDownQuickCanvas(); 137 | return true; 138 | } 139 | }); 140 | 141 | initPopuWindowLayout(); 142 | 143 | drawingBoardView.setPickColorListener(new DrawingBoardView.OnPickColorListener() { 144 | @Override 145 | public void onPickColor(int color) { 146 | viewColor.setBackgroundColor(color); 147 | } 148 | }); 149 | 150 | } 151 | 152 | @OnClick({R.id.bt_draw_sketch, R.id.bt_draw_colours, R.id.bt_zoom_up, R.id.bt_zoom_down, R.id.bt_save, R.id.iv_image_selector, R.id.iv_delete, R.id.iv_eraser, R.id.iv_brush, R.id.iv_undo, R.id.iv_redo}) 153 | public void onClick(View view) { 154 | switch (view.getId()) { 155 | case R.id.bt_draw_sketch: 156 | drawingBoardView.drawSketch(); 157 | break; 158 | case R.id.bt_draw_colours: 159 | drawingBoardView.drawColours(); 160 | break; 161 | case R.id.iv_undo: 162 | drawingBoardView.undo(); 163 | break; 164 | case R.id.iv_redo: 165 | drawingBoardView.redo(); 166 | break; 167 | case R.id.bt_zoom_up: 168 | drawingBoardView.zoomUpCanvas(); 169 | break; 170 | case R.id.bt_zoom_down: 171 | drawingBoardView.zoomDownCanvas(); 172 | break; 173 | case R.id.iv_eraser: 174 | if (ivEraser.getAlpha() < 1f) { 175 | ivEraser.setAlpha(1f); 176 | ivBrush.setAlpha(0.5f); 177 | } 178 | showPopup(viewPopu, DrawingBoardView.ERASER); 179 | break; 180 | case R.id.iv_brush: 181 | if (ivBrush.getAlpha() < 1f) { 182 | ivBrush.setAlpha(1f); 183 | ivEraser.setAlpha(0.5f); 184 | } 185 | mColorPicker.setColor(drawingBoardView.getStrokeColor()); 186 | showPopup(viewPopu, DrawingBoardView.STROKE); 187 | break; 188 | 189 | case R.id.bt_save: 190 | if (drawingBoardView.getPaths().size() == 0) { 191 | Toast.makeText(MainActivity.this, "你还没有手绘", Toast.LENGTH_SHORT).show(); 192 | return; 193 | } 194 | bitmap = drawingBoardView.getDrawBitmap(); 195 | startActivity(new Intent(MainActivity.this, ImageActivity.class)); 196 | break; 197 | case R.id.iv_image_selector: 198 | openImageSelector(); 199 | break; 200 | case R.id.iv_delete: 201 | new MaterialDialog.Builder(MainActivity.this) 202 | .content("擦除手绘") 203 | .positiveText("确认") 204 | .callback(new MaterialDialog.ButtonCallback() { 205 | @Override 206 | public void onPositive(MaterialDialog dialog) { 207 | viewColor.setBackgroundColor(Color.BLACK); 208 | drawingBoardView.erase(); 209 | } 210 | }) 211 | .build().show(); 212 | break; 213 | } 214 | } 215 | 216 | private ImageView strokeImageView, eraserImageView; 217 | private int size; 218 | private ColorPicker mColorPicker; 219 | private View popupLayout, popupEraserLayout; 220 | 221 | private void initPopuWindowLayout() { 222 | 223 | // Inflate the popup_layout.xml 224 | LayoutInflater inflater = (LayoutInflater) getSystemService(AppCompatActivity 225 | .LAYOUT_INFLATER_SERVICE); 226 | popupLayout = inflater.inflate(R.layout.popup_sketch_stroke, null); 227 | // And the one for eraser 228 | LayoutInflater inflaterEraser = (LayoutInflater) getSystemService(AppCompatActivity 229 | .LAYOUT_INFLATER_SERVICE); 230 | popupEraserLayout = inflaterEraser.inflate(R.layout.popup_sketch_eraser, null); 231 | 232 | // Actual stroke shape size is retrieved 233 | strokeImageView = (ImageView) popupLayout.findViewById(R.id.stroke_circle); 234 | final Drawable circleDrawable = getResources().getDrawable(R.drawable.circle); 235 | size = circleDrawable.getIntrinsicWidth(); 236 | // Actual eraser shape size is retrieved 237 | eraserImageView = (ImageView) popupEraserLayout.findViewById(R.id.stroke_circle); 238 | size = circleDrawable.getIntrinsicWidth(); 239 | 240 | setSeekbarProgress(DrawingBoardView.DEFAULT_ERASER_SIZE, DrawingBoardView.ERASER); 241 | setSeekbarProgress(DrawingBoardView.DEFAULT_STROKE_SIZE, DrawingBoardView.STROKE); 242 | 243 | // Stroke color picker initialization and event managing 244 | mColorPicker = (ColorPicker) popupLayout.findViewById(R.id.stroke_color_picker); 245 | mColorPicker.addSVBar((SVBar) popupLayout.findViewById(R.id.svbar)); 246 | mColorPicker.addOpacityBar((OpacityBar) popupLayout.findViewById(R.id.opacitybar)); 247 | 248 | mColorPicker.setOnColorChangedListener(new ColorPicker.OnColorChangedListener() { 249 | @Override 250 | public void onColorChanged(int color) { 251 | drawingBoardView.setStrokeColor(color); 252 | viewColor.setBackgroundColor(color); 253 | } 254 | }); 255 | mColorPicker.setColor(drawingBoardView.getStrokeColor()); 256 | mColorPicker.setOldCenterColor(drawingBoardView.getStrokeColor()); 257 | } 258 | 259 | 260 | private int seekBarStrokeProgress, seekBarEraserProgress; 261 | private int oldColor; 262 | 263 | // The method that displays the popup. 264 | private void showPopup(View anchor, final int eraserOrStroke) { 265 | 266 | boolean isErasing = eraserOrStroke == DrawingBoardView.ERASER; 267 | 268 | oldColor = mColorPicker.getColor(); 269 | 270 | DisplayMetrics metrics = new DisplayMetrics(); 271 | getWindowManager().getDefaultDisplay().getMetrics(metrics); 272 | 273 | // Creating the PopupWindow 274 | PopupWindow popup = new PopupWindow(this); 275 | popup.setContentView(isErasing ? popupEraserLayout : popupLayout); 276 | popup.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); 277 | popup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); 278 | popup.setFocusable(true); 279 | popup.setOnDismissListener(new PopupWindow.OnDismissListener() { 280 | @Override 281 | public void onDismiss() { 282 | if (mColorPicker.getColor() != oldColor) 283 | mColorPicker.setOldCenterColor(oldColor); 284 | } 285 | }); 286 | 287 | // Clear the default translucent background 288 | popup.setBackgroundDrawable(new BitmapDrawable()); 289 | 290 | // Displaying the popup at the specified location, + offsets (transformed 291 | // dp to pixel to support multiple screen sizes) 292 | popup.showAsDropDown(findViewById(R.id.view_popu)); 293 | 294 | // Stroke size seekbar initialization and event managing 295 | SeekBar mSeekBar; 296 | mSeekBar = (SeekBar) (isErasing ? popupEraserLayout 297 | .findViewById(R.id.stroke_seekbar) : popupLayout 298 | .findViewById(R.id.stroke_seekbar)); 299 | mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 300 | @Override 301 | public void onStopTrackingTouch(SeekBar seekBar) { 302 | } 303 | 304 | 305 | @Override 306 | public void onStartTrackingTouch(SeekBar seekBar) { 307 | } 308 | 309 | 310 | @Override 311 | public void onProgressChanged(SeekBar seekBar, int progress, 312 | boolean fromUser) { 313 | // When the seekbar is moved a new size is calculated and the new shape 314 | // is positioned centrally into the ImageView 315 | setSeekbarProgress(progress, eraserOrStroke); 316 | } 317 | }); 318 | int progress = isErasing ? seekBarEraserProgress : seekBarStrokeProgress; 319 | mSeekBar.setProgress(progress); 320 | setSeekbarProgress(progress, eraserOrStroke); 321 | 322 | } 323 | 324 | 325 | protected void setSeekbarProgress(int progress, int eraserOrStroke) { 326 | int calcProgress = progress > 1 ? progress : 1; 327 | 328 | int newSize = Math.round((size / 100f) * calcProgress); 329 | int offset = Math.round((size - newSize) / 2); 330 | 331 | 332 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(newSize, newSize); 333 | lp.setMargins(offset, offset, offset, offset); 334 | if (eraserOrStroke == DrawingBoardView.STROKE) { 335 | strokeImageView.setLayoutParams(lp); 336 | seekBarStrokeProgress = progress; 337 | } else { 338 | eraserImageView.setLayoutParams(lp); 339 | seekBarEraserProgress = progress; 340 | } 341 | 342 | drawingBoardView.setSize(newSize, eraserOrStroke); 343 | Log.e("PPP", "newSize:" + newSize + " , eraserOrStroke:" + eraserOrStroke); 344 | } 345 | 346 | 347 | public void openImageSelector() { 348 | ImageConfig imageConfig = new ImageConfig.Builder(new ImageLoader() { 349 | @Override 350 | public void displayImage(Context context, String path, ImageView imageView) { 351 | Glide.with(context) 352 | .load(path) 353 | .placeholder(com.yancy.imageselector.R.mipmap.imageselector_photo) 354 | .centerCrop() 355 | .into(imageView); 356 | } 357 | }) 358 | .steepToolBarColor(getResources().getColor(R.color.blue)) 359 | .titleBgColor(getResources().getColor(R.color.blue)) 360 | .titleSubmitTextColor(getResources().getColor(R.color.white)) 361 | .titleTextColor(getResources().getColor(R.color.white)) 362 | //截屏 363 | //.crop() 364 | // 开启单选 (默认为多选) 365 | .singleSelect() 366 | // 开启拍照功能 (默认关闭) 367 | .showCamera() 368 | // 拍照后存放的图片路径(默认 /temp/picture) (会自动创建) 369 | .filePath("/DrawingBoard/Pictures") 370 | .build(); 371 | 372 | ImageSelector.open(this, imageConfig); // 开启图片选择器 373 | } 374 | 375 | 376 | @Override 377 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 378 | if (requestCode == ImageSelector.IMAGE_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null) { 379 | // Get Image Path List 380 | List pathList = data.getStringArrayListExtra(ImageSelectorActivity.EXTRA_RESULT); 381 | for (String path : pathList) { 382 | Log.d("ImagePathList", path); 383 | Glide.with(this).load(path) 384 | .asBitmap() 385 | .centerCrop() 386 | .into(new SimpleTarget() { 387 | 388 | @Override 389 | public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) { 390 | drawingBoardView.setDrawBgBitmap(bitmap); 391 | } 392 | 393 | }); 394 | } 395 | } 396 | 397 | } 398 | 399 | @Override 400 | public void onBackPressed() { 401 | new MaterialDialog.Builder(MainActivity.this) 402 | .content("是否退出应用 ?") 403 | .positiveText("确认") 404 | .negativeText("取消") 405 | .callback(new MaterialDialog.ButtonCallback() { 406 | @Override 407 | public void onPositive(MaterialDialog dialog) { 408 | finish(); 409 | } 410 | }) 411 | .build().show(); 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/view/DrawPathData.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw.view; 2 | 3 | import android.graphics.Paint; 4 | import android.graphics.Path; 5 | 6 | /** 7 | * Created by zhangyiipeng on 2018/7/6. 8 | */ 9 | 10 | public class DrawPathData { 11 | private Paint mPaint; 12 | private int mColor; 13 | private float mStrokeWidth; 14 | private Path mPath; 15 | 16 | public DrawPathData(Path path, Paint paint) { 17 | mPaint = paint; 18 | mPath = path; 19 | 20 | mStrokeWidth = paint.getStrokeWidth(); 21 | mColor = paint.getColor(); 22 | } 23 | 24 | public Paint getPaint() { 25 | mPaint.setColor(mColor); 26 | mPaint.setStrokeWidth(mStrokeWidth); 27 | return mPaint; 28 | } 29 | 30 | public int getColor() { 31 | return mColor; 32 | } 33 | 34 | public void setColor(int color) { 35 | mColor = color; 36 | } 37 | 38 | public Path getPath() { 39 | return mPath; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/view/DrawingBoardView.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.annotation.SuppressLint; 7 | import android.content.Context; 8 | import android.graphics.Bitmap; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Matrix; 12 | import android.graphics.Paint; 13 | import android.graphics.PaintFlagsDrawFilter; 14 | import android.graphics.Path; 15 | import android.graphics.PorterDuff; 16 | import android.graphics.Rect; 17 | import android.os.Build; 18 | import android.support.annotation.NonNull; 19 | import android.support.annotation.Nullable; 20 | import android.util.AttributeSet; 21 | import android.util.Log; 22 | import android.view.GestureDetector; 23 | import android.view.Gravity; 24 | import android.view.MotionEvent; 25 | import android.view.ScaleGestureDetector; 26 | import android.view.ViewTreeObserver; 27 | import android.view.animation.LinearInterpolator; 28 | import android.widget.FrameLayout; 29 | 30 | import com.almeros.android.multitouch.RotateGestureDetector; 31 | 32 | import java.util.ArrayList; 33 | import java.util.LinkedList; 34 | import java.util.List; 35 | 36 | import jp.co.cyberagent.android.gpuimage.GPUImage; 37 | import jp.co.cyberagent.android.gpuimage.GPUImageSketchFilter; 38 | 39 | /** 40 | * Created by zhangyiipeng on 2018/7/6. 41 | */ 42 | 43 | public class DrawingBoardView extends FrameLayout { 44 | 45 | public static final String TAG = "DrawingBoardView"; 46 | 47 | public static final int STROKE = 0; 48 | public static final int ERASER = 1; 49 | public static final int DEFAULT_STROKE_SIZE = 7; 50 | public static final int DEFAULT_ERASER_SIZE = 50; 51 | //画布最大缩放比率 52 | private static float MAX_ZOOM_FACTOR = 40f; 53 | //画布最小缩放比率 54 | private static float MIN_ZOOM_FACTOR = 0.8f; 55 | //touch move canvas rate 56 | private static float MOVE_CANVAS_RATE = 8.0f; 57 | //预览view相对canvas的缩放比率 58 | private static float PREVIEW_SCALE_RATE = 4.0f; 59 | //取色view相对canvas的缩放比率 60 | private final float PICK_COLOR_SCALE_RATE = 3.0f; 61 | //蒙层透明度 62 | public static final int COVER_ALPHA = 100; 63 | private List allDrawPathDatas = new ArrayList<>(); 64 | private List undoDrawPathDatas = new LinkedList<>(); 65 | private Paint mPaint; 66 | private Paint mDrawPaint; 67 | private Path mDrawPath; 68 | private Bitmap mDrawBitmap; 69 | private Canvas mDrawCanvas; 70 | private Rect mClipBoundsRect; 71 | private Paint mCoverPaint; 72 | 73 | private Bitmap mBottomBitmap; 74 | private GPUImage mGpuImage; 75 | //原始bitmap 76 | private Bitmap mSourceBitmap; 77 | //素描bitmap 78 | private Bitmap mSketchBitmap; 79 | 80 | private ScaleGestureDetector mScaleGestureDetector; 81 | private GestureDetector mGestureDetector; 82 | private TouchColorPickView mColorPickView; 83 | private PreviewRegionView mPreviewRegionView; 84 | private ValueAnimator mZoomCanvasAnimator; 85 | 86 | private int strokeColor = Color.BLACK; 87 | //是否在绘制预览view 88 | private boolean isPreviewRegion = false; 89 | //是否绘制Path 90 | private boolean isDrawPath = false; 91 | private ObjectAnimator mPreviewViewHideAnimator; 92 | private ObjectAnimator mPreviewViewShowAnimator; 93 | private RotateGestureDetector mRotateGestureDetector; 94 | private float mMoveX; 95 | 96 | public DrawingBoardView(@NonNull Context context) { 97 | this(context, null); 98 | } 99 | 100 | public DrawingBoardView(@NonNull Context context, @Nullable AttributeSet attrs) { 101 | this(context, attrs, 0); 102 | } 103 | 104 | public DrawingBoardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 105 | super(context, attrs, defStyleAttr); 106 | init(); 107 | } 108 | 109 | private void init() { 110 | setWillNotDraw(false); 111 | // setBackgroundColor(Color.WHITE); 112 | // setLayerType(View.LAYER_TYPE_SOFTWARE, null); 113 | //默认画笔 114 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 115 | //设置画笔抗锯齿和抗抖动 116 | mPaint.setAntiAlias(true); 117 | mPaint.setStyle(Paint.Style.STROKE); //设置画笔为实心 118 | mPaint.setStrokeCap(Paint.Cap.ROUND); //设置画笔笔触为圆形 119 | mPaint.setStrokeJoin(Paint.Join.ROUND); //设置画笔接触点为圆形 120 | mPaint.setStrokeWidth(DEFAULT_STROKE_SIZE); //画笔的宽度 121 | mPaint.setColor(strokeColor); //画笔的颜色 122 | //绘制画笔 123 | mDrawPaint = new Paint(mPaint); 124 | //绘制路径 125 | mDrawPath = new Path(); 126 | //蒙层画笔 127 | mCoverPaint = new Paint(); 128 | mCoverPaint.setColor(Color.WHITE); 129 | mCoverPaint.setAlpha(COVER_ALPHA); 130 | //缩放后剪切边距 131 | mClipBoundsRect = new Rect(); 132 | 133 | mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener()); 134 | mGestureDetector = new GestureDetector(getContext(), new GestureListener()); 135 | mRotateGestureDetector = new RotateGestureDetector(getContext(), new RotateGestureDetectorListener()); 136 | getViewTreeObserver().addOnGlobalLayoutListener( 137 | new ViewTreeObserver.OnGlobalLayoutListener() { 138 | 139 | @SuppressLint("NewApi") 140 | @SuppressWarnings("deprecation") 141 | @Override 142 | public void onGlobalLayout() { 143 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 144 | getViewTreeObserver() 145 | .removeGlobalOnLayoutListener(this); 146 | } else { 147 | getViewTreeObserver() 148 | .removeOnGlobalLayoutListener(this); 149 | } 150 | initPreviewRegionView(); 151 | 152 | initColorPickView(); 153 | } 154 | 155 | }); 156 | 157 | } 158 | 159 | /** 160 | * 获取当前已经缩放的比例 161 | * 162 | * @return 因为x方向和y方向比例相同,所以只返回x方向的缩放比例即可 163 | */ 164 | private float getZoomRate() { 165 | if (mScaleMatrix == null) 166 | return 1; 167 | float[] values = new float[9]; 168 | mScaleMatrix.getValues(values); 169 | return values[Matrix.MSCALE_X]; 170 | 171 | } 172 | 173 | /** 174 | * 获取当前旋转角度 175 | * 176 | */ 177 | private float getRotationDegrees() { 178 | if (mScaleMatrix == null) 179 | return 0; 180 | float[] values = new float[9]; 181 | mScaleMatrix.getValues(values); 182 | return values[Matrix.MSKEW_X]; 183 | 184 | } 185 | 186 | 187 | private class Point{ 188 | public float x; 189 | public float y; 190 | public Point(float x, float y) { 191 | this.x = x; 192 | this.y = y; 193 | } 194 | 195 | } 196 | 197 | 198 | private Point calcNewPoint(Point p, Point pCenter, float angle) { 199 | // calc arc 200 | float l = (float) ((angle * Math.PI) / 180); 201 | //sin/cos value 202 | float cosv = (float) Math.cos(l); 203 | float sinv = (float) Math.sin(l); 204 | 205 | // calc new point 206 | float newX = (float) ((p.x - pCenter.x) * cosv - (p.y - pCenter.y) * sinv + pCenter.x); 207 | float newY = (float) ((p.x - pCenter.x) * sinv + (p.y - pCenter.y) * cosv + pCenter.y); 208 | return new Point(newX, newY); 209 | } 210 | 211 | 212 | 213 | private int preMotionEvent = -1; 214 | 215 | private float preMoveX; 216 | private float preMoveY; 217 | 218 | 219 | private int mLastPoint; 220 | 221 | private float centerX; 222 | private float centerY; 223 | @Override 224 | public boolean onTouchEvent(MotionEvent event) { 225 | mScaleGestureDetector.onTouchEvent(event); 226 | mRotateGestureDetector.onTouchEvent(event); 227 | mGestureDetector.onTouchEvent(event); 228 | 229 | float touchX = 0; 230 | float touchY = 0; 231 | //获得多点个数,也叫屏幕上手指的个数 232 | int pointCount = event.getPointerCount(); 233 | 234 | if (pointCount > 1) { 235 | for (int i = 0; i < pointCount; i++) { 236 | touchX += event.getX(i); 237 | touchY += event.getY(i); 238 | } 239 | 240 | //求出中心点的位置 241 | touchX /= pointCount; 242 | touchY /= pointCount; 243 | 244 | centerX = touchX; 245 | centerY = touchY; 246 | 247 | } else { 248 | touchX = event.getX() / getZoomRate() + mClipBoundsRect.left; 249 | touchY = event.getY() / getZoomRate() + mClipBoundsRect.top; 250 | 251 | // float cx = centerX / getZoomRate() + mClipBoundsRect.left; 252 | // float cy = centerY / getZoomRate() + mClipBoundsRect.top; 253 | // final float rotationDegrees = (float) (getRotationDegrees() * 180 / Math.PI); 254 | // Log.e(TAG, "rotationDegrees :"+rotationDegrees); 255 | // final Point point = calcNewPoint(new Point(touchX, touchY), new Point(centerX, centerY), rotationDegrees); 256 | // 257 | // touchX = point.x; 258 | // touchY = point.y; 259 | 260 | } 261 | //如果手指的数量发生了改变,则不移动 262 | if (mLastPoint != pointCount) { 263 | preMoveX = touchX; 264 | preMoveY = touchY; 265 | } 266 | mLastPoint = pointCount; 267 | 268 | switch (event.getAction()) { 269 | case MotionEvent.ACTION_DOWN: 270 | preMotionEvent = MotionEvent.ACTION_DOWN; 271 | 272 | preMoveX = touchX; 273 | preMoveY = touchY; 274 | mDrawPath.moveTo(touchX, touchY); 275 | colorPick(touchX, touchY, event.getX(), event.getY()); 276 | break; 277 | case MotionEvent.ACTION_MOVE: 278 | if ((preMotionEvent == MotionEvent.ACTION_DOWN || 279 | preMotionEvent == MotionEvent.ACTION_MOVE)) { 280 | preMotionEvent = MotionEvent.ACTION_MOVE; 281 | float mMoveX = touchX - preMoveX; 282 | float moveY = touchY - preMoveY; 283 | 284 | colorPick(touchX, touchY, event.getX(), event.getY()); 285 | double d = Math.sqrt(mMoveX * mMoveX + moveY * moveY); 286 | if (d > 5) { 287 | if (!isLongPress && !isMoving) { 288 | if (pointCount > 1) { 289 | mScaleMatrix.postTranslate(mMoveX, moveY); 290 | }else { 291 | mDrawPath.quadTo(preMoveX, preMoveY, (touchX + preMoveX) / 2, (touchY + preMoveY) / 2); 292 | } 293 | isDrawPathDatas = false; 294 | isDrawPath = true; 295 | invalidate(); 296 | } else { 297 | isDrawPath = false; 298 | } 299 | 300 | preMoveX = touchX; 301 | preMoveY = touchY; 302 | } 303 | } 304 | break; 305 | case MotionEvent.ACTION_UP: 306 | preMotionEvent = MotionEvent.ACTION_UP; 307 | if (isLongPress) { 308 | isLongPress = false; 309 | hideColorPickView(); 310 | invalidate(); 311 | 312 | } 313 | Log.d(TAG, "isDrawPath: " + isDrawPath); 314 | if (isDrawPath) { 315 | if (!isMoving && mDrawCanvas != null && pointCount==1) { 316 | mDrawCanvas.drawPath(mDrawPath, mDrawPaint); 317 | invalidate(); 318 | } 319 | isDrawPath = false; 320 | undoDrawPathDatas.clear(); 321 | isRedo = false; 322 | allDrawPathDatas.add(new DrawPathData(new Path(mDrawPath), mDrawPaint)); 323 | mDrawPath.reset(); 324 | isUndo = true; 325 | if (undoRedoListener != null) undoRedoListener.onUndoRedo(isUndo, isRedo); 326 | } 327 | break; 328 | } 329 | return true; 330 | } 331 | 332 | 333 | private boolean isLongPress; 334 | 335 | private class GestureListener extends GestureDetector.SimpleOnGestureListener { 336 | @Override 337 | public boolean onDoubleTap(final MotionEvent event) { 338 | isDrawPathDatas = false; 339 | mZoomCenterX = event.getX() / mZoomFactor + mClipBoundsRect.left; 340 | mZoomCenterY = event.getY() / mZoomFactor + mClipBoundsRect.top; 341 | zoomUpCanvas(); 342 | return true; 343 | } 344 | 345 | public void onLongPress(MotionEvent e) { 346 | isLongPress = true; 347 | invalidate(); 348 | } 349 | } 350 | 351 | private class RotateGestureDetectorListener implements RotateGestureDetector.OnRotateGestureListener { 352 | @Override 353 | public boolean onRotate(RotateGestureDetector detector) { 354 | final float rotationDegreesDelta = detector.getRotationDegreesDelta(); 355 | Log.e(TAG,"rotationDegreesDelta : "+rotationDegreesDelta); 356 | // mScaleMatrix.postRotate(-rotationDegreesDelta,centerX,centerY); 357 | // invalidate(); 358 | return true; 359 | } 360 | 361 | @Override 362 | public boolean onRotateBegin(RotateGestureDetector detector) { 363 | return true; 364 | } 365 | 366 | @Override 367 | public void onRotateEnd(RotateGestureDetector detector) { 368 | 369 | } 370 | } 371 | 372 | 373 | private Matrix mScaleMatrix = new Matrix(); 374 | private float mZoomFactor = 1.0f; 375 | private float mZoomCenterX = -1.0f; 376 | private float mZoomCenterY = -1.0f; 377 | 378 | private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 379 | @Override 380 | public boolean onScale(ScaleGestureDetector detector) { 381 | isDrawPathDatas = false; 382 | isPreviewRegion = false; 383 | 384 | float scaleFactor = detector.getScaleFactor(); 385 | Log.e(TAG, "onScale : " + scaleFactor); 386 | mZoomFactor *= scaleFactor; 387 | Log.e(TAG, "mZoomFactor : " + mZoomFactor); 388 | mZoomFactor = Math.max(MIN_ZOOM_FACTOR, Math.min(mZoomFactor, MAX_ZOOM_FACTOR)); 389 | mZoomFactor = mZoomFactor > MAX_ZOOM_FACTOR ? MAX_ZOOM_FACTOR : mZoomFactor < MIN_ZOOM_FACTOR ? MIN_ZOOM_FACTOR : mZoomFactor; 390 | 391 | float mFocusX = detector.getFocusX(); 392 | float mFocusY = detector.getFocusY(); 393 | 394 | Log.w(TAG, "mZoomCenterX:" + mZoomCenterX + " , mZoomCenterY:" + mZoomCenterY); 395 | 396 | mScaleMatrix.postScale(scaleFactor, scaleFactor, mFocusX, mFocusY); 397 | 398 | mZoomCenterX = mFocusX / getZoomRate() + mClipBoundsRect.left; 399 | mZoomCenterY = mFocusY / getZoomRate() + mClipBoundsRect.top; 400 | 401 | 402 | invalidate(); 403 | 404 | return true; 405 | } 406 | 407 | @Override 408 | public void onScaleEnd(ScaleGestureDetector detector) { 409 | super.onScaleEnd(detector); 410 | if (mZoomFactor < 1.0f) { 411 | mZoomFactor = 1.0f; 412 | } 413 | invalidate(); 414 | } 415 | } 416 | 417 | 418 | private float ratioCenterX; 419 | private float ratioCenterY; 420 | 421 | @Override 422 | public void onDraw(Canvas canvas) { 423 | super.onDraw(canvas); 424 | 425 | if (mDrawBitmap == null) { 426 | //创建绘制bitmap 427 | mDrawBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 428 | //创建白色的bitmap,作为绘制的最底层背景色 429 | mBottomBitmap = mDrawBitmap.copy(Bitmap.Config.ARGB_8888, true); 430 | mBottomBitmap.eraseColor(Color.WHITE); 431 | //绘制Canvas 432 | mDrawCanvas = new Canvas(mDrawBitmap); 433 | //Canvas抗抗锯齿 434 | mDrawCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); 435 | //计算初始的中心点坐标 436 | calculateXY(); 437 | } 438 | 439 | canvas.save(); 440 | //画布缩放 441 | Log.w(TAG, "mZoomCenterX:" + mZoomCenterX + " , mZoomCenterY:" + mZoomCenterY); 442 | canvas.setMatrix(mScaleMatrix); 443 | 444 | 445 | // canvas 放大或縮小後,drawRect會產生偏移,你要计算缩放比例,getClipBounds能得到两个顶点的坐标, 根据两个顶点的坐标的比例来确定坐标 446 | canvas.getClipBounds(mClipBoundsRect); 447 | Log.d(TAG, "mZoomCenterX:" + mZoomCenterX + " , mZoomCenterY:" + mZoomCenterY); 448 | Log.d(TAG, "centerX:" + mClipBoundsRect.centerX() + " , centerY:" + mClipBoundsRect.centerY()); 449 | 450 | //mClipBoundsRect.centerX()与mZoomCenterX成一定比率,这个对计算位置很关键 451 | ratioCenterX = mZoomCenterX / mClipBoundsRect.centerX(); 452 | ratioCenterY = mZoomCenterY / mClipBoundsRect.centerY(); 453 | Log.d(TAG, "ratioCenterX : " + ratioCenterX + " , ratioCenterY :" + ratioCenterY); 454 | 455 | canvas.drawBitmap(mBottomBitmap, 0, 0, null);//底色 456 | if (bgBitmap != null) { 457 | if (isDrawSketch) { 458 | mDrawPaint.setAlpha(alpha); 459 | canvas.drawBitmap(mSketchBitmap, bgBitmapTranslateX, bgBitmapTranslateY, null); 460 | canvas.drawBitmap(mSourceBitmap, bgBitmapTranslateX, bgBitmapTranslateY, mDrawPaint); 461 | } else { 462 | mDrawPaint.setAlpha(alpha); 463 | canvas.drawBitmap(mSourceBitmap, bgBitmapTranslateX, bgBitmapTranslateY, null); 464 | canvas.drawBitmap(mSketchBitmap, bgBitmapTranslateX, bgBitmapTranslateY, mDrawPaint); 465 | } 466 | mDrawPaint.setAlpha(255); 467 | } 468 | 469 | if (!isLongPress) { 470 | canvas.drawRect(mClipBoundsRect, mCoverPaint);//蒙层 471 | if (isDrawPathDatas){ 472 | mDrawCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//绘制透明色 473 | } 474 | canvas.drawBitmap(mDrawBitmap, 0, 0, null);//绘画 475 | Log.d(TAG, "isDrawPathDatas:" + isDrawPathDatas + " , allDrawPathDatas:" + allDrawPathDatas.size()); 476 | if (isDrawPathDatas) { 477 | for (DrawPathData drawPathData : allDrawPathDatas) { 478 | canvas.drawPath(drawPathData.getPath(), drawPathData.getPaint()); 479 | mDrawCanvas.drawPath(drawPathData.getPath(), drawPathData.getPaint()); 480 | } 481 | } else { 482 | canvas.drawPath(mDrawPath, mDrawPaint); 483 | } 484 | } 485 | 486 | canvas.restore(); 487 | 488 | if (mPreviewRegionView != null && !isPreviewRegion) { 489 | //绘制右上角的预览区域 490 | mPreviewRegionView.drawPreviewRegion(mDrawBitmap, bgBitmap, bgBitmapTranslateX, bgBitmapTranslateY, mClipBoundsRect, 4.0f); 491 | } 492 | 493 | } 494 | 495 | private int bgBitmapTranslateX = 0; 496 | private int bgBitmapTranslateY = 0; 497 | private int drawBitmapTranslateX = 0; 498 | private int drawBitmapTranslateY = 0; 499 | 500 | private void calculateXY() { 501 | if (!isDrawPathDatas) { 502 | mClipBoundsRect = new Rect(0, 0, getWidth(), getHeight()); 503 | mZoomCenterX = mClipBoundsRect.centerX(); 504 | mZoomCenterY = mClipBoundsRect.centerY(); 505 | } 506 | 507 | if (mDrawBitmap.getWidth() < mDrawBitmap.getHeight()) { 508 | drawBitmapTranslateX = mClipBoundsRect.centerX() - (mDrawBitmap.getWidth() / 2); 509 | } else { 510 | drawBitmapTranslateY = mClipBoundsRect.centerY() - (mDrawBitmap.getHeight() / 2); 511 | } 512 | } 513 | 514 | 515 | public interface OnPickColorListener { 516 | void onPickColor(int color); 517 | } 518 | 519 | private OnPickColorListener mPickColorListener; 520 | 521 | public void setPickColorListener(OnPickColorListener listener) { 522 | this.mPickColorListener = listener; 523 | } 524 | 525 | public interface OnUndoRedoListener { 526 | void onUndoRedo(boolean isUndo, boolean isRedo); 527 | } 528 | 529 | private OnUndoRedoListener undoRedoListener; 530 | 531 | public void setOnUndoRedoListener(OnUndoRedoListener listener) { 532 | this.undoRedoListener = listener; 533 | } 534 | 535 | /** 536 | * 初始化预览view 537 | */ 538 | private void initPreviewRegionView() { 539 | mPreviewRegionView = new PreviewRegionView(getContext()); 540 | FrameLayout.LayoutParams layoutParams = new LayoutParams(getWidth() / 4, getHeight() / 4, 541 | Gravity.TOP | Gravity.END); 542 | mPreviewRegionView.setLayoutParams(layoutParams); 543 | mPreviewRegionView.setOnZoomRegionListener(new PreviewRegionView.OnZoomRegionListener() { 544 | @Override 545 | public void onZoomRegionMoved(Rect newRect) { 546 | isPreviewRegion = true; 547 | 548 | mZoomCenterX = newRect.centerX() * 4 * ratioCenterX; 549 | mZoomCenterY = newRect.centerY() * 4 * ratioCenterY; 550 | 551 | } 552 | }); 553 | addView(mPreviewRegionView); 554 | showPreviewRegionView(); 555 | } 556 | 557 | /** 558 | * 显示预览view 559 | */ 560 | private synchronized void showPreviewRegionView() { 561 | if (mPreviewViewShowAnimator == null) { 562 | mPreviewViewShowAnimator = ObjectAnimator.ofFloat(mPreviewRegionView, "alpha", 0f, 1f); 563 | mPreviewViewShowAnimator.setDuration(500); 564 | } 565 | if (mPreviewRegionView != null && mPreviewRegionView.getVisibility() == INVISIBLE) { 566 | mPreviewRegionView.setVisibility(VISIBLE); 567 | mPreviewViewShowAnimator.start(); 568 | } 569 | } 570 | 571 | /** 572 | * 隐藏预览view 573 | */ 574 | private synchronized void hidePreviewRegionView() { 575 | mZoomCenterX = mClipBoundsRect.centerX(); 576 | mZoomCenterY = mClipBoundsRect.centerY(); 577 | if (mPreviewViewHideAnimator == null) { 578 | mPreviewViewHideAnimator = ObjectAnimator.ofFloat(mPreviewRegionView, "alpha", 1f, 0f); 579 | mPreviewViewHideAnimator.setDuration(500); 580 | mPreviewViewHideAnimator.addListener(new Animator.AnimatorListener() { 581 | @Override 582 | public void onAnimationStart(Animator animation) { 583 | } 584 | 585 | @Override 586 | public void onAnimationEnd(Animator animation) { 587 | mPreviewRegionView.setVisibility(INVISIBLE); 588 | } 589 | 590 | @Override 591 | public void onAnimationCancel(Animator animation) { 592 | } 593 | 594 | @Override 595 | public void onAnimationRepeat(Animator animation) { 596 | } 597 | }); 598 | } 599 | if (mPreviewRegionView != null && mPreviewRegionView.getVisibility() == VISIBLE) { 600 | mPreviewViewHideAnimator.start(); 601 | } 602 | } 603 | 604 | /** 605 | * 初始化取色view 606 | */ 607 | private void initColorPickView() { 608 | mColorPickView = new TouchColorPickView(getContext()); 609 | FrameLayout.LayoutParams layoutParams = new LayoutParams((int) (getWidth() / PICK_COLOR_SCALE_RATE), (int) (getWidth() / PICK_COLOR_SCALE_RATE), 610 | Gravity.TOP | Gravity.LEFT); 611 | mColorPickView.setLayoutParams(layoutParams); 612 | mColorPickView.setPickBitmapColor(strokeColor, null, 1); 613 | addView(mColorPickView); 614 | mColorPickView.setVisibility(GONE); 615 | } 616 | 617 | /** 618 | * 显示取色view 619 | * 620 | * @param eventX 屏幕实际触摸点x 621 | * @param eventY 屏幕实际触摸点y 622 | * @param pickBitmapWidth 取色bitmap width 623 | * @param pickBitmapHeight 取色bitmap height 624 | * @param pickBitmap 取色bitmap 625 | */ 626 | private void showColorPickView(float eventX, float eventY, int pickBitmapWidth, int pickBitmapHeight, Bitmap pickBitmap) { 627 | // ColorPickView跟随手指移动,位置在手指的正上方,若想恰好在手指触摸位置的下方,可以把 *1.5f 改为 /2.0f 628 | mColorPickView.setTranslationX(eventX - pickBitmapWidth / 2.0f); 629 | mColorPickView.setTranslationY(eventY - pickBitmapHeight * 1.5f); 630 | 631 | if (mColorPickView != null && mColorPickView.getVisibility() == GONE) { 632 | mColorPickView.setVisibility(VISIBLE); 633 | } 634 | mColorPickView.setPickBitmapColor(strokeColor, pickBitmap, mZoomFactor); 635 | mDrawPaint.setColor(strokeColor); 636 | if (mPickColorListener != null) { 637 | mPickColorListener.onPickColor(strokeColor); 638 | } 639 | } 640 | 641 | /** 642 | * 隐藏取色view 643 | */ 644 | private void hideColorPickView() { 645 | if (mColorPickView != null && mColorPickView.getVisibility() == VISIBLE) { 646 | mColorPickView.setVisibility(GONE); 647 | } 648 | } 649 | 650 | /** 651 | * 获取触摸点颜色及bitmap 652 | * 653 | * @param touchX 654 | * @param touchY 655 | * @param eventX 656 | * @param eventY 657 | */ 658 | private void colorPick(float touchX, float touchY, float eventX, float eventY) { 659 | if (isLongPress && bgBitmap != null) { 660 | float x = touchX - bgBitmapTranslateX; 661 | float y = touchY - bgBitmapTranslateY; 662 | if (x > 0 && x < bgBitmap.getWidth() && y > 0 && y < bgBitmap.getHeight()) { 663 | int color = bgBitmap.getPixel((int) x, (int) y); 664 | int r = Color.red(color); 665 | int g = Color.green(color); 666 | int b = Color.blue(color); 667 | int a = Color.alpha(color); 668 | Log.d(TAG, "a=" + a + ",r=" + r + ",g=" + g + ",b=" + b); 669 | 670 | strokeColor = Color.argb(a, r, g, b); 671 | 672 | int[] pixels = new int[bgBitmap.getWidth() * bgBitmap.getHeight()]; 673 | int pickBitmapWidth = (int) (getWidth() / PICK_COLOR_SCALE_RATE); 674 | int pickBitmapHeight = pickBitmapWidth; 675 | int bX = (int) x - pickBitmapWidth / 2; 676 | int bY = (int) y - pickBitmapHeight / 2; 677 | Bitmap pickBitmap = null; 678 | Log.d("colorPick", "bX:" + bX + "bY:" + bY + ",pickBitmapWidth:" + pickBitmapWidth + ",pickBitmapHeight:" + pickBitmapHeight); 679 | Log.d("colorPick", "bgBitmap.getWidth()" + bgBitmap.getWidth() + ",bgBitmap.getHeight():" + bgBitmap.getHeight()); 680 | if (bX >= 0 && bY >= 0) { 681 | if (bX + pickBitmapWidth <= bgBitmap.getWidth() && bY + pickBitmapHeight <= bgBitmap.getHeight()) { 682 | Log.d("colorPick", "pick color sucess"); 683 | bgBitmap.getPixels(pixels, 0, bgBitmap.getWidth(), bX, bY, pickBitmapWidth, pickBitmapHeight); 684 | pickBitmap = Bitmap.createBitmap(pixels, 0, bgBitmap.getWidth(), bgBitmap.getWidth(), bgBitmap.getHeight(), Bitmap.Config.ARGB_8888); 685 | } 686 | } 687 | Log.d("TouchXY", "x:" + x + ", y:" + y); 688 | showColorPickView(eventX, eventY, pickBitmapWidth, pickBitmapHeight, pickBitmap); 689 | } 690 | } 691 | } 692 | 693 | /** 694 | * 放大画布 695 | * 696 | */ 697 | public void zoomUpCanvas() { 698 | zoomCanvas(1.03f); 699 | } 700 | 701 | /** 702 | * 缩小画布 703 | * 704 | */ 705 | public void zoomDownCanvas() { 706 | zoomCanvas(0.97f); 707 | } 708 | 709 | 710 | /** 711 | * 快速缩放画布 712 | * 713 | */ 714 | public void zoomUpQuickCanvas() { 715 | // zoomCanvas(true, MAX_ZOOM_FACTOR); 716 | } 717 | public void zoomDownQuickCanvas() { 718 | // zoomCanvas(false, MAX_ZOOM_FACTOR); 719 | } 720 | 721 | /** 722 | * 缩放画布 723 | * @param scaleFactor 724 | */ 725 | public void zoomCanvas( float scaleFactor) { 726 | this.mScaleFactor = scaleFactor; 727 | zoomCanvasAnim(); 728 | } 729 | 730 | private float mScaleFactor = 1; 731 | private void zoomCanvasAnim() { 732 | if (mZoomCanvasAnimator == null) { 733 | mZoomCanvasAnimator = ValueAnimator.ofFloat(); 734 | mZoomCanvasAnimator.setDuration(300); 735 | mZoomCanvasAnimator.setInterpolator(new LinearInterpolator()); 736 | mZoomCanvasAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 737 | @Override 738 | public void onAnimationUpdate(ValueAnimator animation) { 739 | 740 | mScaleMatrix.postScale(mScaleFactor,mScaleFactor,mZoomCenterX,mZoomCenterY); 741 | 742 | mPreviewRegionView.moveDestView(mClipBoundsRect, PREVIEW_SCALE_RATE); 743 | 744 | invalidate(); 745 | } 746 | }); 747 | } 748 | mZoomCanvasAnimator.setFloatValues(10, 1); 749 | if (!mZoomCanvasAnimator.isRunning()){ 750 | mZoomCanvasAnimator.start(); 751 | } 752 | } 753 | 754 | /** 755 | * 移动画布 756 | * 757 | * @param mx 758 | * @param my 759 | */ 760 | private boolean isMoving; 761 | public void moveCanvas(float mx, float my,boolean isMoving) { 762 | isDrawPathDatas = false; 763 | this.isMoving = isMoving; 764 | //移动canvas时需要reset path ,因为 invalidate()会绘制最后一笔path 765 | mDrawPath.reset(); 766 | 767 | mScaleMatrix.postTranslate(mx* 2,my* 2); 768 | 769 | mPreviewRegionView.moveDestView(mClipBoundsRect, PREVIEW_SCALE_RATE); 770 | 771 | invalidate(); 772 | 773 | Log.d(TAG, "left : " + mClipBoundsRect.left + " ,top : " + mClipBoundsRect.top + " ,right : " + mClipBoundsRect.right + " ,bottom : " + mClipBoundsRect.bottom); 774 | 775 | } 776 | 777 | private boolean isUndo; 778 | private boolean isRedo; 779 | private boolean isDrawPathDatas; 780 | 781 | /** 782 | * 撤销 783 | */ 784 | public void undo() { 785 | if (allDrawPathDatas.size() > 0) { 786 | undoDrawPathDatas.add(allDrawPathDatas.remove(allDrawPathDatas.size() - 1)); 787 | isRedo = true; 788 | if (allDrawPathDatas.size() > 0) { 789 | isUndo = true; 790 | } else { 791 | isUndo = false; 792 | } 793 | invalidate(); 794 | isDrawPathDatas = true; 795 | } else { 796 | isUndo = false; 797 | isDrawPathDatas = false; 798 | } 799 | if (undoRedoListener != null) undoRedoListener.onUndoRedo(isUndo, isRedo); 800 | } 801 | 802 | /** 803 | * 恢复 804 | */ 805 | public void redo() { 806 | if (undoDrawPathDatas.size() > 0) { 807 | isUndo = true; 808 | allDrawPathDatas.add(undoDrawPathDatas.remove(undoDrawPathDatas.size() - 1)); 809 | if (undoDrawPathDatas.size() > 0) { 810 | isRedo = true; 811 | } else { 812 | isRedo = false; 813 | } 814 | invalidate(); 815 | isDrawPathDatas = true; 816 | } else { 817 | isRedo = false; 818 | isDrawPathDatas = false; 819 | } 820 | if (undoRedoListener != null) undoRedoListener.onUndoRedo(isUndo, isRedo); 821 | } 822 | 823 | /** 824 | * 擦除 825 | */ 826 | public void erase() { 827 | allDrawPathDatas.clear(); 828 | undoDrawPathDatas.clear(); 829 | // 先判断是否已经回收 830 | if (mDrawBitmap != null && !mDrawBitmap.isRecycled()) { 831 | // 回收并且置为null 832 | mDrawBitmap.recycle(); 833 | mDrawBitmap = null; 834 | } 835 | if (bgBitmap != null && !bgBitmap.isRecycled()) { 836 | // 回收并且置为null 837 | bgBitmap.recycle(); 838 | bgBitmap = null; 839 | } 840 | mDrawPaint = null; 841 | mDrawPaint = new Paint(mPaint); 842 | mDrawPath.reset(); 843 | mDrawCanvas = null; 844 | isDrawPathDatas = false; 845 | isRedo = false; 846 | isUndo = false; 847 | if (mScaleMatrix != null) { 848 | mScaleMatrix.reset(); 849 | } 850 | 851 | if (undoRedoListener != null) undoRedoListener.onUndoRedo(isUndo, isRedo); 852 | System.gc(); 853 | invalidate(); 854 | } 855 | 856 | 857 | private boolean isDrawSketch; 858 | 859 | /** 860 | * 绘制素描 861 | */ 862 | public void drawSketch() { 863 | if (bgBitmap != null && !isDrawSketch) { 864 | isPreviewRegion = false; 865 | isDrawSketch = true; 866 | bgBitmap = mSketchBitmap; 867 | sketchSwitchAnim(); 868 | } 869 | } 870 | 871 | /** 872 | * 绘制颜色 873 | */ 874 | public void drawColours() { 875 | if (bgBitmap != null && isDrawSketch) { 876 | isPreviewRegion = false; 877 | bgBitmap = mSourceBitmap; 878 | isDrawSketch = false; 879 | sketchSwitchAnim(); 880 | } 881 | } 882 | 883 | /** 884 | * 素描与原画切换动画 885 | */ 886 | private void sketchSwitchAnim() { 887 | sketchSwitchAnim(500); 888 | } 889 | 890 | /** 891 | * 素描与原画切换动画 892 | * 893 | * @param duration 894 | */ 895 | private int alpha = 0; 896 | 897 | private void sketchSwitchAnim(long duration) { 898 | ValueAnimator animator = ValueAnimator.ofInt(255, 0); 899 | animator.setDuration(duration); 900 | animator.setInterpolator(new LinearInterpolator()); 901 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 902 | @Override 903 | public void onAnimationUpdate(ValueAnimator animation) { 904 | alpha = (int) animation.getAnimatedValue(); 905 | Log.i("AAA", "alpha:" + alpha); 906 | invalidate(); 907 | } 908 | }); 909 | animator.start(); 910 | } 911 | 912 | private Bitmap bgBitmap; 913 | 914 | public void setDrawBgBitmap(Bitmap bitmap) { 915 | erase(); 916 | 917 | float scaleRatio = 1.0f; 918 | int width = bitmap.getWidth(); 919 | int height = bitmap.getHeight(); 920 | 921 | float screenRatio = 1.0f; 922 | float imgRatio = (float) height / (float) width; 923 | float drawRatio = (float) getHeight() / (float) getWidth(); 924 | if (imgRatio >= screenRatio && drawRatio < imgRatio) { 925 | //高度大于屏幕,以高为准 926 | scaleRatio = (float) getHeight() / (float) height; 927 | } else { 928 | scaleRatio = (float) getWidth() / (float) width; 929 | } 930 | 931 | Matrix matrix = new Matrix(); 932 | matrix.postScale(scaleRatio, scaleRatio); 933 | mSourceBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); 934 | 935 | if (mGpuImage == null) { 936 | mGpuImage = new GPUImage(getContext()); 937 | mGpuImage.setFilter(new GPUImageSketchFilter()); 938 | } 939 | 940 | mSketchBitmap = mGpuImage.getBitmapWithFilterApplied(mSourceBitmap); 941 | 942 | //默认素描 943 | bgBitmap = mSketchBitmap; 944 | 945 | isDrawSketch = true; 946 | 947 | mZoomFactor = 1; 948 | 949 | 950 | mClipBoundsRect = new Rect(0, 0, getWidth(), getHeight()); 951 | if (bgBitmap.getWidth() < bgBitmap.getHeight() && drawRatio < imgRatio) { 952 | bgBitmapTranslateX = mClipBoundsRect.centerX() - (bgBitmap.getWidth() / 2); 953 | bgBitmapTranslateY = 0; 954 | } else { 955 | bgBitmapTranslateX = 0; 956 | bgBitmapTranslateY = mClipBoundsRect.centerY() - (bgBitmap.getHeight() / 2); 957 | } 958 | 959 | sketchSwitchAnim(1500); 960 | } 961 | 962 | private int strokeSize = DEFAULT_STROKE_SIZE; 963 | private int eraserSize = DEFAULT_ERASER_SIZE; 964 | 965 | public void setSize(int size, int eraserOrStroke) { 966 | switch (eraserOrStroke) { 967 | case ERASER: 968 | eraserSize = size; 969 | mDrawPaint.setColor(Color.WHITE); 970 | mDrawPaint.setStrokeWidth(eraserSize); 971 | break; 972 | case STROKE: 973 | strokeSize = size; 974 | mDrawPaint.setColor(strokeColor); 975 | mDrawPaint.setStrokeWidth(strokeSize); 976 | break; 977 | } 978 | } 979 | 980 | /** 981 | * 设置画笔颜色 982 | * 983 | * @param color 984 | */ 985 | public void setStrokeColor(int color) { 986 | this.strokeColor = color; 987 | mDrawPaint.setColor(color); 988 | } 989 | 990 | /** 991 | * 获取当前画笔颜色 992 | * 993 | * @return 994 | */ 995 | public int getStrokeColor() { 996 | return strokeColor; 997 | } 998 | 999 | public List getPaths() { 1000 | return allDrawPathDatas; 1001 | } 1002 | 1003 | public Bitmap getDrawBitmap() { 1004 | return mDrawBitmap; 1005 | } 1006 | 1007 | } 1008 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/view/MoveRegionView.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.widget.RelativeLayout; 9 | 10 | /** 11 | * Created by zhangyiipeng on 2018/7/6. 12 | */ 13 | 14 | public class MoveRegionView extends RelativeLayout implements View.OnTouchListener { 15 | 16 | public MoveRegionView(Context context) { 17 | this(context, null); 18 | } 19 | 20 | public MoveRegionView(Context context, AttributeSet attrs) { 21 | this(context, attrs, 0); 22 | } 23 | 24 | public MoveRegionView(Context context, AttributeSet attrs, int defStyleAttr) { 25 | super(context, attrs, defStyleAttr); 26 | init(); 27 | } 28 | 29 | private void init() { 30 | setOnTouchListener(this); 31 | } 32 | 33 | public interface OnTouchMoveListener { 34 | void onTouchMove(float mx, float my,boolean isMovind); 35 | } 36 | 37 | private OnTouchMoveListener mTouchMoveListener; 38 | 39 | public void setTouchMoveListener(OnTouchMoveListener touchMoveListener) { 40 | mTouchMoveListener = touchMoveListener; 41 | } 42 | 43 | @Override 44 | protected void onDraw(Canvas canvas) { 45 | super.onDraw(canvas); 46 | } 47 | 48 | private int mCurrentMotionEvent = -1; 49 | 50 | private float mStartTouchX, mStartTouchY; 51 | 52 | @Override 53 | public boolean onTouch(View v, MotionEvent event) { 54 | float touchX = event.getX(); 55 | float touchY = event.getY(); 56 | 57 | switch (event.getActionMasked()) { 58 | case MotionEvent.ACTION_DOWN: 59 | mCurrentMotionEvent = MotionEvent.ACTION_DOWN; 60 | 61 | 62 | break; 63 | 64 | case MotionEvent.ACTION_MOVE: 65 | if ((mCurrentMotionEvent == MotionEvent.ACTION_DOWN 66 | || mCurrentMotionEvent == MotionEvent.ACTION_MOVE)) { 67 | mCurrentMotionEvent = MotionEvent.ACTION_MOVE; 68 | 69 | if (mTouchMoveListener != null && mStartTouchX != 0 && mStartTouchY != 0) { 70 | mTouchMoveListener.onTouchMove(touchX - mStartTouchX, touchY - mStartTouchY,true); 71 | } 72 | 73 | mStartTouchX = touchX; 74 | mStartTouchY = touchY; 75 | 76 | } 77 | break; 78 | 79 | case MotionEvent.ACTION_UP: 80 | mCurrentMotionEvent = MotionEvent.ACTION_UP; 81 | if (mTouchMoveListener != null && mStartTouchX != 0 && mStartTouchY != 0) { 82 | mTouchMoveListener.onTouchMove(0, 0,false); 83 | } 84 | 85 | mStartTouchX = 0; 86 | mStartTouchY = 0; 87 | break; 88 | } 89 | return true; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/view/PreviewRegionView.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw.view; 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.Matrix; 8 | import android.graphics.Paint; 9 | import android.graphics.Rect; 10 | import android.support.v7.widget.AppCompatImageView; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | 16 | /** 17 | * Created by zhangyiipeng on 2018/7/6. 18 | */ 19 | 20 | public class PreviewRegionView extends AppCompatImageView implements View.OnTouchListener { 21 | 22 | public static final String TAG = "PreviewRegionView"; 23 | 24 | private Paint mPaint; 25 | private Bitmap mPreviewBitmap; 26 | private Canvas mPreviewCanvas; 27 | private Paint ClipBoundsPaint; 28 | 29 | private OnZoomRegionListener mOnZoomRegionListener; 30 | 31 | public void setOnZoomRegionListener(OnZoomRegionListener onZoomRegionListener) { 32 | mOnZoomRegionListener = onZoomRegionListener; 33 | } 34 | 35 | public interface OnZoomRegionListener { 36 | void onZoomRegionMoved(Rect newRect); 37 | } 38 | 39 | public PreviewRegionView(Context context) { 40 | this(context, null); 41 | } 42 | 43 | public PreviewRegionView(Context context, AttributeSet attrs) { 44 | this(context, attrs, 0); 45 | } 46 | 47 | public PreviewRegionView(Context context, AttributeSet attrs, int defStyleAttr) { 48 | super(context, attrs, defStyleAttr); 49 | init(); 50 | } 51 | 52 | private void init() { 53 | setOnTouchListener(this); 54 | 55 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 56 | mPaint.setColor(Color.BLACK); 57 | mPaint.setAlpha(60); 58 | 59 | ClipBoundsPaint = new Paint(); 60 | ClipBoundsPaint.setColor(Color.WHITE); 61 | ClipBoundsPaint.setAlpha(180); 62 | } 63 | 64 | @Override 65 | protected void onDraw(Canvas canvas) { 66 | super.onDraw(canvas); 67 | if (mPreviewCanvas == null || mPreviewBitmap == null) { 68 | mDestRect = new Rect(0, 0, getWidth(), getHeight()); 69 | mPreviewBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 70 | mPreviewCanvas = new Canvas(mPreviewBitmap); 71 | } 72 | 73 | if (mClipBoundsRect != null) { 74 | if (mBgBitmap != null) { 75 | canvas.drawBitmap(scaleImageBitmap(mBgBitmap), left, top, null); 76 | } 77 | canvas.drawBitmap(mDrawBitmap, mSourceRect, mDestRect, null); 78 | 79 | mPreviewBitmap.eraseColor(Color.TRANSPARENT); 80 | mPreviewCanvas.drawRect(mDestRect, mPaint); 81 | mPreviewCanvas.drawRect(mClipBoundsRect, ClipBoundsPaint);//中心点指示器区域 82 | 83 | canvas.drawBitmap(mPreviewBitmap, 0, 0, null); 84 | } 85 | 86 | super.onDraw(canvas); 87 | } 88 | 89 | public void moveDestView(Rect clipBoundsRect, float scaleFactor) { 90 | this.mClipBoundsRect = new Rect((int) (clipBoundsRect.left / scaleFactor), (int) (clipBoundsRect.top / scaleFactor), 91 | (int) (clipBoundsRect.right / scaleFactor), (int) (clipBoundsRect.bottom / scaleFactor)); 92 | 93 | invalidate(); 94 | } 95 | 96 | private int mCurrentMotionEvent = -1; 97 | private boolean mMoveZoomArea = false; 98 | 99 | private float mStartTouchX, mStartTouchY; 100 | 101 | @Override 102 | public boolean onTouch(View v, MotionEvent event) { 103 | float touchX = event.getX(); 104 | float touchY = event.getY(); 105 | 106 | switch (event.getActionMasked()) { 107 | case MotionEvent.ACTION_DOWN: 108 | mCurrentMotionEvent = MotionEvent.ACTION_DOWN; 109 | mMoveZoomArea = false; 110 | 111 | if (touchX >= mClipBoundsRect.left && touchX <= mClipBoundsRect.right 112 | && touchY >= mClipBoundsRect.top && touchY <= mClipBoundsRect.bottom) { 113 | mMoveZoomArea = true; 114 | mStartTouchX = touchX; 115 | mStartTouchY = touchY; 116 | } 117 | 118 | break; 119 | 120 | case MotionEvent.ACTION_MOVE: 121 | if ((mCurrentMotionEvent == MotionEvent.ACTION_DOWN 122 | || mCurrentMotionEvent == MotionEvent.ACTION_MOVE) && mMoveZoomArea) { 123 | mCurrentMotionEvent = MotionEvent.ACTION_MOVE; 124 | Rect preview = new Rect( 125 | mClipBoundsRect.left + (int) (touchX - mStartTouchX), 126 | mClipBoundsRect.top + (int) (touchY - mStartTouchY), 127 | mClipBoundsRect.right + (int) ((touchX - mStartTouchX)), 128 | mClipBoundsRect.bottom + (int) ((touchY - mStartTouchY))); 129 | 130 | if (preview.left >= 0 && preview.right <= getWidth() 131 | && preview.top >= 0 && preview.bottom <= getHeight()) { 132 | mClipBoundsRect = preview; 133 | invalidate(); 134 | } 135 | if (mOnZoomRegionListener != null) { 136 | mOnZoomRegionListener.onZoomRegionMoved(mClipBoundsRect); 137 | } 138 | 139 | mStartTouchX = touchX; 140 | mStartTouchY = touchY; 141 | 142 | } 143 | break; 144 | 145 | case MotionEvent.ACTION_UP: 146 | mCurrentMotionEvent = MotionEvent.ACTION_UP; 147 | mMoveZoomArea = false; 148 | break; 149 | } 150 | return true; 151 | } 152 | 153 | private Bitmap mDrawBitmap; 154 | private Bitmap mBgBitmap; 155 | 156 | private Rect mClipBoundsRect; 157 | private Rect mSourceRect; 158 | private Rect mDestRect; 159 | 160 | private int left; 161 | private int top; 162 | 163 | public void drawPreviewRegion(Bitmap drawBitmap, Bitmap bgBitmap, int left, int top, Rect clipBoundsRect, float scaleFactor) { 164 | this.mDrawBitmap = drawBitmap; 165 | this.mBgBitmap = bgBitmap; 166 | this.left = (int) (left / scaleFactor); 167 | this.top = (int) (top / scaleFactor); 168 | this.mClipBoundsRect = new Rect((int) (clipBoundsRect.left / scaleFactor), (int) (clipBoundsRect.top / scaleFactor), 169 | (int) (clipBoundsRect.right / scaleFactor), (int) (clipBoundsRect.bottom / scaleFactor)); 170 | 171 | mSourceRect = new Rect(0, 0, mDrawBitmap.getWidth(), mDrawBitmap.getHeight()); 172 | 173 | invalidate(); 174 | } 175 | 176 | public Bitmap scaleImageBitmap(Bitmap bitmap) { 177 | float scaleRatio = 1; 178 | int width = bitmap.getWidth(); 179 | int height = bitmap.getHeight(); 180 | float screenRatio = 1.0f; 181 | float imgRatio = (float) height / (float) width; 182 | float drawRatio = (float) getHeight() / (float) getWidth(); 183 | if (imgRatio >= screenRatio && drawRatio < imgRatio) { 184 | //高度大于屏幕,以高为准 185 | scaleRatio = (float) getHeight() / (float) height; 186 | } else { 187 | scaleRatio = (float) getWidth() / (float) width; 188 | } 189 | 190 | Matrix matrix = new Matrix(); 191 | matrix.postScale(scaleRatio, scaleRatio); 192 | return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); 193 | 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyp/draw/view/TouchColorPickView.java: -------------------------------------------------------------------------------- 1 | package com.zyp.draw.view; 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.PorterDuff; 9 | import android.graphics.PorterDuffXfermode; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | /** 15 | * Created by zhangyiipeng on 2018/7/6. 16 | */ 17 | 18 | public class TouchColorPickView extends View implements View.OnTouchListener { 19 | 20 | public static final String TAG = "TouchColorPickView"; 21 | private int measuredWidth; 22 | private int measuredHeight; 23 | private Paint paint; 24 | 25 | public TouchColorPickView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public TouchColorPickView(Context context, AttributeSet attrs) { 30 | this(context, attrs, 0); 31 | } 32 | 33 | public TouchColorPickView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | init(); 36 | } 37 | 38 | private void init() { 39 | setOnTouchListener(this); 40 | //设置画笔抗锯齿和抗抖动 41 | paint = new Paint(); 42 | paint.setStrokeWidth(10); //画笔的宽度 43 | paint.setColor(strokeColor); //画笔的颜色 44 | paint.setAntiAlias(true); 45 | paint.setDither(true); 46 | } 47 | 48 | @Override 49 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 50 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 51 | measuredHeight = getMeasuredHeight(); 52 | measuredWidth = getMeasuredWidth(); 53 | } 54 | 55 | private int strokeColor = Color.BLACK; 56 | private float mScaleFactor = 1; 57 | 58 | public void setPickBitmapColor(int color, Bitmap bitmap, float scaleFactor) { 59 | this.strokeColor = color; 60 | this.mBitmap = bitmap; 61 | this.mScaleFactor = scaleFactor; 62 | paint.setColor(strokeColor); 63 | invalidate(); 64 | } 65 | 66 | private Bitmap mBitmap; 67 | private int r = 10; 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | 72 | int centerX = measuredWidth / 2; 73 | int centerY = measuredHeight / 2; 74 | int radius = measuredHeight / 2; 75 | if (mBitmap != null) { 76 | int sc0 = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG); 77 | canvas.drawCircle(centerX, centerY, radius, paint); 78 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 79 | 80 | canvas.save(); 81 | canvas.scale(mScaleFactor * 2, mScaleFactor * 2, centerX, centerY); 82 | canvas.drawBitmap(mBitmap, 0, 0, paint); 83 | canvas.restore(); 84 | //还原 85 | paint.setXfermode(null); 86 | //绘制红色十字中心点 87 | paint.setColor(Color.RED); 88 | paint.setStrokeWidth(1f); 89 | canvas.drawLine(centerX - r, centerY, centerX + r, centerY, paint); 90 | canvas.drawLine(centerX, centerY - r, centerX, centerY + r, paint); 91 | 92 | canvas.restoreToCount(sc0); 93 | } 94 | int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG); 95 | paint.setColor(strokeColor); 96 | canvas.drawCircle(centerX, centerY, radius, paint); 97 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 98 | canvas.drawCircle(centerX, centerY, radius * 3 / 4, paint); 99 | //还原 100 | paint.setXfermode(null); 101 | 102 | canvas.restoreToCount(sc); 103 | super.onDraw(canvas); 104 | } 105 | 106 | 107 | @Override 108 | public boolean onTouch(View v, MotionEvent event) { 109 | return false; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_popup.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzyyppqq/DrawingBoard/51b7fea16e40e0223e6dac56a771b950d87e8a02/app/src/main/res/drawable/bg_popup.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 |