├── .gitignore ├── .idea ├── caches │ ├── build_file_checksums.ser │ └── gradle_models.ser ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── annotation ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── jscheng │ └── annotations │ └── Route.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── jscheng │ │ └── srich │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jscheng │ │ │ └── srich │ │ │ ├── BaseActivity.java │ │ │ ├── BaseApplication.java │ │ │ ├── MainActivity.java │ │ │ ├── NoteService.java │ │ │ ├── NoteServicePool.java │ │ │ ├── converter │ │ │ ├── StyleCode.java │ │ │ ├── decoder │ │ │ │ ├── ParagraphDecoder.java │ │ │ │ └── WordStyleNode.java │ │ │ └── encoder │ │ │ │ └── ParagraphEncoder.java │ │ │ ├── dao │ │ │ ├── NoteDao.java │ │ │ └── NoteDboOpener.java │ │ │ ├── editor │ │ │ ├── INoteEditorManager.java │ │ │ ├── NoteEditorClickListener.java │ │ │ ├── NoteEditorConfig.java │ │ │ ├── NoteEditorInputConnection.java │ │ │ ├── NoteEditorManager.java │ │ │ ├── NoteEditorManagerImpl.java │ │ │ ├── NoteEditorRender.java │ │ │ ├── NoteEditorSelectionListener.java │ │ │ ├── NoteEditorText.java │ │ │ ├── render │ │ │ │ ├── NoteLineSpanRender.java │ │ │ │ ├── NoteWordSpanRender.java │ │ │ │ ├── line_render │ │ │ │ │ ├── NoteBulletLineSpanRender.java │ │ │ │ │ ├── NoteCheckBoxSpanRender.java │ │ │ │ │ ├── NoteDividingSpanRender.java │ │ │ │ │ ├── NoteImageSpanRender.java │ │ │ │ │ ├── NoteIndentationSpanRender.java │ │ │ │ │ ├── NoteNumSpanRender.java │ │ │ │ │ └── NoteUncheckBoxSpanRender.java │ │ │ │ └── word_render │ │ │ │ │ ├── NoteBackgroundSpanRender.java │ │ │ │ │ ├── NoteBoldSpanRender.java │ │ │ │ │ ├── NoteItalicSpanRender.java │ │ │ │ │ ├── NoteStrikethroughSpanRender.java │ │ │ │ │ ├── NoteSubscriptSpanRender.java │ │ │ │ │ ├── NoteSuperscriptSpanRender.java │ │ │ │ │ └── NoteUnderlineSpanRender.java │ │ │ └── spans │ │ │ │ ├── NoteBackgroundSpan.java │ │ │ │ ├── NoteBoldSpan.java │ │ │ │ ├── NoteBulletSpan.java │ │ │ │ ├── NoteCheckBoxSpan.java │ │ │ │ ├── NoteClickSpan.java │ │ │ │ ├── NoteDividingLineSpan.java │ │ │ │ ├── NoteImageSpan.java │ │ │ │ ├── NoteIndentationSpan.java │ │ │ │ ├── NoteItalicSpan.java │ │ │ │ ├── NoteNumSpan.java │ │ │ │ ├── NoteStrikethroughSpan.java │ │ │ │ ├── NoteSubscriptSpan.java │ │ │ │ ├── NoteSuperscriptSpan.java │ │ │ │ ├── NoteTypefaceSpan.java │ │ │ │ ├── NoteUnderLineSpan.java │ │ │ │ └── NoteUriSpan.java │ │ │ ├── image_loader │ │ │ ├── DownSampler.java │ │ │ ├── FileImageJob.java │ │ │ ├── HttpImageJob.java │ │ │ ├── ImageDiskCache.java │ │ │ ├── ImageFetcher.java │ │ │ ├── ImageFetcherCallback.java │ │ │ ├── ImageGlobalListener.java │ │ │ ├── ImageJob.java │ │ │ ├── ImageJobCallback.java │ │ │ ├── ImageJobFactory.java │ │ │ ├── ImageKeyFactory.java │ │ │ ├── ImageLoader.java │ │ │ ├── ImageMemoryCache.java │ │ │ ├── ImageTarget.java │ │ │ └── ImageViewTarget.java │ │ │ ├── image_preview │ │ │ └── ImagePreviewActivity.java │ │ │ ├── model │ │ │ ├── Note.java │ │ │ ├── NoteBuilder.java │ │ │ ├── NoteModel.java │ │ │ ├── NoteSnap.java │ │ │ ├── NoteSnapBuilder.java │ │ │ ├── Options.java │ │ │ ├── OutLine.java │ │ │ ├── Paragraph.java │ │ │ ├── ParagraphBuilder.java │ │ │ └── Style.java │ │ │ ├── mvp │ │ │ ├── IModel.java │ │ │ ├── IPresenter.java │ │ │ └── IView.java │ │ │ ├── note_edit │ │ │ ├── EditNoteActivity.java │ │ │ ├── EditNoteFormatDialog.java │ │ │ ├── EditNoteInputDialog.java │ │ │ ├── EditNoteMode.java │ │ │ ├── EditNotePresenter.java │ │ │ ├── EditNoteToolbar.java │ │ │ ├── FloatEditButton.java │ │ │ └── NoteEditorBar.java │ │ │ ├── outline │ │ │ ├── FloatNewButton.java │ │ │ ├── OutLineCenterDialog.java │ │ │ ├── OutLineModel.java │ │ │ ├── OutLinePresenter.java │ │ │ ├── OutLinesActivity.java │ │ │ └── OutLinesAdapter.java │ │ │ ├── revoke │ │ │ └── NoteRevocationManager.java │ │ │ ├── route │ │ │ ├── ActivityInterceptor.java │ │ │ ├── IRouteInfo.java │ │ │ ├── LogInterceptor.java │ │ │ ├── RouteInfoUtil.java │ │ │ ├── Router.java │ │ │ ├── RouterChain.java │ │ │ ├── RouterInterceptor.java │ │ │ ├── RouterRequest.java │ │ │ ├── RouterResponse.java │ │ │ └── UriAnalyzerInterceptor.java │ │ │ ├── utils │ │ │ ├── ClipboardUtil.java │ │ │ ├── DateUtil.java │ │ │ ├── DisplayUtil.java │ │ │ ├── FontUtil.java │ │ │ ├── KeyboardUtil.java │ │ │ ├── MdUtil.java │ │ │ ├── OsUtil.java │ │ │ ├── PermissionUtil.java │ │ │ ├── StorageUtil.java │ │ │ ├── UriPathUtil.java │ │ │ └── VersionUtil.java │ │ │ └── widget │ │ │ ├── CircularProgressView.java │ │ │ └── PinchImageView.java │ └── res │ │ ├── anim │ │ ├── edit_note_bottom_in_anim.xml │ │ └── edit_note_bottom_out_anim.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── editor_bar_format_select.xml │ │ ├── editor_bar_redo_select.xml │ │ ├── editor_bar_undo_select.xml │ │ ├── editor_note_bottom_dialog_bg.xml │ │ ├── editor_note_dialog_edit.xml │ │ ├── editor_note_dialog_input.xml │ │ ├── editor_note_dialog_text_left.xml │ │ ├── editor_note_dialog_text_right.xml │ │ ├── editor_view_select.xml │ │ ├── ic_launcher_background.xml │ │ └── outline_toolbar_line.xml │ │ ├── layout │ │ ├── activity_editnote.xml │ │ ├── activity_image_preview.xml │ │ ├── activity_main.xml │ │ ├── activity_outline.xml │ │ ├── edit_note_bottom_dialog.xml │ │ ├── edit_note_editor_bar.xml │ │ ├── edit_note_input_dialog.xml │ │ ├── edit_note_toolbar.xml │ │ ├── outline_center_dialog.xml │ │ ├── outline_item_view_date.xml │ │ ├── outline_item_view_note.xml │ │ ├── outline_toolbar.xml │ │ └── outline_toolbar2.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 │ │ ├── ic_compose.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_note_edit_attach.png │ │ ├── ic_note_edit_back.png │ │ ├── ic_note_edit_backward.png │ │ ├── ic_note_edit_check.png │ │ ├── ic_note_edit_edit.png │ │ ├── ic_note_edit_error.png │ │ ├── ic_note_edit_format_off.png │ │ ├── ic_note_edit_forward.png │ │ ├── ic_note_edit_loading.png │ │ ├── ic_note_edit_more.png │ │ ├── ic_note_edit_redo.png │ │ ├── ic_note_edit_redo_disabled.png │ │ ├── ic_note_edit_tick.png │ │ ├── ic_note_edit_uncheck.png │ │ ├── ic_note_edit_undo.png │ │ └── ic_note_edit_undo_disabled.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_note_edit_format_on.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── jscheng │ └── srich │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── processor ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── jscheng │ └── processor │ ├── AnnotatedClass.java │ ├── RouteClass.java │ └── RouterProcessor.java ├── readme.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /annotation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /annotation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | } 6 | 7 | sourceCompatibility = "1.8" 8 | targetCompatibility = "1.8" 9 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/jscheng/annotations/Route.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Created By Chengjunsen on 2019/3/18 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.CLASS) 13 | public @interface Route { 14 | String value() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.jscheng.srich" 7 | minSdkVersion 23 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | javaCompileOptions{ 13 | annotationProcessorOptions.includeCompileClasspath = true 14 | } 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | implementation 'com.android.support:appcompat-v7:28.0.0' 27 | implementation 'com.android.support:recyclerview-v7:28.0.0' 28 | implementation 'com.android.support:design:28.0.0' 29 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 33 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.5' 34 | implementation 'com.squareup.okio:okio:2.1.0' 35 | implementation 'com.squareup.okhttp3:okhttp:3.12.0' 36 | annotationProcessor "android.arch.lifecycle:compiler:1.1.1" 37 | implementation 'com.jakewharton:disklrucache:2.0.2' 38 | implementation "io.reactivex.rxjava2:rxjava:2.2.7" 39 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 40 | implementation 'com.android.support:exifinterface:28.0.0' 41 | implementation project(':annotation') 42 | annotationProcessor project(':processor') 43 | } 44 | -------------------------------------------------------------------------------- /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/jscheng/srich/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 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() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.jscheng.srich", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/20 7 | */ 8 | public abstract class BaseActivity extends AppCompatActivity { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/BaseApplication.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 2 | 3 | import android.app.Application; 4 | 5 | import com.jscheng.srich.route.Router; 6 | 7 | public class BaseApplication extends Application { 8 | 9 | @Override 10 | public void onCreate() { 11 | super.onCreate(); 12 | Router.init(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.jscheng.annotations.Route; 6 | 7 | @Route("main") 8 | public class MainActivity extends BaseActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/NoteService.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.app.Service; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.os.IBinder; 9 | import android.support.annotation.Nullable; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/3/7 13 | */ 14 | public class NoteService extends Service implements Application.ActivityLifecycleCallbacks{ 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | getApplication().registerActivityLifecycleCallbacks(this); 20 | } 21 | 22 | @Override 23 | public int onStartCommand(Intent intent, int flags, int startId) { 24 | return Service.START_STICKY; 25 | } 26 | 27 | @Nullable 28 | @Override 29 | public IBinder onBind(Intent intent) { 30 | return null; 31 | } 32 | 33 | @Override 34 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 35 | 36 | } 37 | 38 | @Override 39 | public void onActivityStarted(Activity activity) { 40 | 41 | } 42 | 43 | @Override 44 | public void onActivityResumed(Activity activity) { 45 | 46 | } 47 | 48 | @Override 49 | public void onActivityPaused(Activity activity) { 50 | 51 | } 52 | 53 | @Override 54 | public void onActivityStopped(Activity activity) { 55 | 56 | } 57 | 58 | @Override 59 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 60 | 61 | } 62 | 63 | @Override 64 | public void onActivityDestroyed(Activity activity) { 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/NoteServicePool.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.jscheng.srich.model.Note; 6 | 7 | import java.util.concurrent.ArrayBlockingQueue; 8 | import java.util.concurrent.ThreadFactory; 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * Created By Chengjunsen on 2019/3/7 14 | */ 15 | public class NoteServicePool { 16 | 17 | private ThreadPoolExecutor mExecutors; 18 | private int corePoolSize = 2; 19 | private int maximumPoolSize = 5; 20 | private int keepLive = 10; 21 | 22 | public static NoteServicePool mInstance; 23 | 24 | public static NoteServicePool getInstance() { 25 | if (mInstance == null) { 26 | synchronized (NoteServicePool.class) { 27 | if (mInstance == null) { 28 | mInstance = new NoteServicePool(); 29 | } 30 | } 31 | } 32 | return mInstance; 33 | } 34 | 35 | public NoteServicePool() { 36 | mExecutors = new ThreadPoolExecutor(corePoolSize, 37 | maximumPoolSize, 38 | keepLive, 39 | TimeUnit.SECONDS, 40 | new ArrayBlockingQueue(50), new ThreadFactory() { 41 | @Override 42 | public Thread newThread(@NonNull Runnable r) { 43 | Thread thread = new Thread(r); 44 | thread.setName("NoteService " + thread.getId()); 45 | return thread; 46 | } 47 | }); 48 | } 49 | 50 | public void updateNote(Note mNote) { 51 | mExecutors.execute(new Runnable() { 52 | @Override 53 | public void run() { 54 | 55 | } 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/converter/StyleCode.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.converter; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/11 5 | */ 6 | public class StyleCode { 7 | public static final String BoldBegin = ""; 8 | public static final String BoldEnd = ""; 9 | 10 | public static final String ItalicBegin = ""; 11 | public static final String ItalicEnd = ""; 12 | 13 | public static final String UnderLineBegin = ""; 14 | public static final String UnderLineEnd = ""; 15 | 16 | public static final String StrikethroughBegin = ""; 17 | public static final String StrikethroughEnd = ""; 18 | 19 | public static final String BackgroudColorBegin = ""; 20 | public static final String BackgroudColorEnd = ""; 21 | 22 | public static final String SuperScriptBegin = ""; 23 | public static final String SuperScriptEnd = ""; 24 | 25 | public static final String SubScriptBegin = ""; 26 | public static final String SubScriptEnd = ""; 27 | 28 | public static final String ImageBegin = ""; 29 | public static final String ImageEnd = ""; 30 | 31 | public static final String CheckBox = ""; 32 | 33 | public static final String UnCheckBox = ""; 34 | 35 | public static final String NumList = "
    "; 36 | 37 | public static final String Bullet = "
      "; 38 | 39 | public static final String DividingLine = "
      "; 40 | 41 | public static final String Indentation = ""; 42 | 43 | public static final String Paragraph = "
      "; 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/converter/decoder/WordStyleNode.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.converter.decoder; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.jscheng.srich.model.Style; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Created By Chengjunsen on 2019/3/11 12 | */ 13 | public class WordStyleNode { 14 | private int style; 15 | private String content; 16 | 17 | public WordStyleNode(int lineStyle, String content) { 18 | this.style = lineStyle; 19 | this.content = content; 20 | } 21 | 22 | public List splitStyle(List nodes, String beginStyleCode, String endStyleCode, int flag) { 23 | List list = new ArrayList<>(); 24 | for (WordStyleNode node: nodes) { 25 | list.addAll(node.splitStyle(beginStyleCode, endStyleCode, flag)); 26 | } 27 | return list; 28 | } 29 | 30 | public List splitStyle(String beginStyleCode, String endStyleCode, int flag) { 31 | List childs = new ArrayList<>(); 32 | String[] temp = content.split(beginStyleCode + "|" + endStyleCode); 33 | for (int i = 0; i< temp.length; i++) { 34 | if (!TextUtils.isEmpty(temp[i])) { 35 | style = Style.setWordStyle(style, (i % 2 != 0), flag); 36 | childs.add(new WordStyleNode(style, temp[i])); 37 | } 38 | } 39 | return childs; 40 | } 41 | 42 | public int getStyle() { 43 | return style; 44 | } 45 | 46 | public String getContent() { 47 | return content; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/dao/NoteDboOpener.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.dao; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteOpenHelper; 6 | 7 | public class NoteDboOpener extends SQLiteOpenHelper { 8 | public static final String DataBaseName = "srich.db"; 9 | public static final int DataBaseVersion = 1; 10 | public static final String NoteTable = "note"; 11 | 12 | public static String DataCreateTableSql = "create table " + NoteTable + " (" + 13 | "id varchar(200) primary key not null, " + 14 | "title varchar(200) , " + 15 | "createTime bigint , " + 16 | "modifyTime bigint ," + 17 | "summary varchar(200) , " + 18 | "summaryImageUrl varchar(200) , " + 19 | "localPath varchar(200)" + 20 | ");"; 21 | 22 | public NoteDboOpener(Context context) { 23 | super(context, DataBaseName, null, DataBaseVersion); 24 | } 25 | 26 | @Override 27 | public void onCreate(SQLiteDatabase db) { 28 | db.execSQL(DataCreateTableSql); 29 | } 30 | 31 | @Override 32 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/INoteEditorManager.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor; 2 | 3 | import android.net.Uri; 4 | 5 | import com.jscheng.srich.model.Note; 6 | import com.jscheng.srich.model.NoteSnap; 7 | 8 | /** 9 | * Created By Chengjunsen on 2019/3/11 10 | */ 11 | public interface INoteEditorManager { 12 | 13 | void commandImage(Uri uri, boolean draw); 14 | 15 | void commandImage(String url, boolean draw); 16 | 17 | void commandColor(boolean isSelected, boolean draw); 18 | 19 | void commandUnderline(boolean isSelected, boolean draw); 20 | 21 | void commandItalic(boolean isSelected, boolean draw); 22 | 23 | void commandBold(boolean isSelected, boolean draw); 24 | 25 | void commandSuperscript(boolean isSelected, boolean draw); 26 | 27 | void commandSubscript(boolean isSelected, boolean draw); 28 | 29 | void commandStrikeThrough(boolean isSelected, boolean draw); 30 | 31 | void commandDividingLine(boolean draw); 32 | 33 | void commandBulletList(boolean isSelected, boolean draw); 34 | 35 | void commandNumList(boolean isSelected, boolean draw); 36 | 37 | void commandCheckBox(boolean isSelected, boolean draw); 38 | 39 | void commandIndentation(boolean draw); 40 | 41 | void commandReduceIndentation(boolean draw); 42 | 43 | void commandDeleteSelection(boolean draw); 44 | 45 | void commandDelete(boolean draw); 46 | 47 | void commandDelete(int num, boolean draw); 48 | 49 | void commandPaste(String content, boolean draw); 50 | 51 | void commandEnter(boolean draw); 52 | 53 | void commandInput(CharSequence content, boolean draw); 54 | 55 | NoteSnap commandRetroke(); 56 | 57 | NoteSnap commandRecover(); 58 | 59 | void addSelectionChangeListener(NoteEditorSelectionListener listener); 60 | 61 | void removeSelectionChangeListener(NoteEditorSelectionListener listener); 62 | 63 | void addClickListener(NoteEditorClickListener listener); 64 | 65 | void requestDraw(); 66 | 67 | void apply(Note mNote, int selectionStart, int selectionEnd); 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/NoteEditorClickListener.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/3/11 7 | */ 8 | public interface NoteEditorClickListener { 9 | void onClickImage(List urls, int index); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/NoteEditorConfig.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/26 5 | */ 6 | public class NoteEditorConfig { 7 | 8 | public static final int HighLightColor = 0xFFCBE5EF; 9 | 10 | public static final int HighLightBackgroundColor = 0xf5f59555; 11 | 12 | public static final int CursorColor = 0xFF000000; 13 | 14 | public static final int HandleColor = 0xFF01A832; 15 | 16 | public static final int TextSizeSp = 16; 17 | 18 | public static final float LetterSpacing = 0.01f; 19 | 20 | public static final int LineSpacing = 15; 21 | 22 | public static final String EndCode = "\n"; 23 | 24 | public static final char EndCodeChar = '\n'; 25 | 26 | public static final String PlaceHoldChar = "\u200B"; 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/NoteEditorInputConnection.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor; 2 | 3 | import android.util.Log; 4 | import android.view.KeyEvent; 5 | import android.view.inputmethod.InputConnectionWrapper; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/1 9 | */ 10 | public class NoteEditorInputConnection extends InputConnectionWrapper { 11 | private static final String TAG = "InputConnection"; 12 | private INoteEditorManager mStyleManager; 13 | 14 | public NoteEditorInputConnection(INoteEditorManager mStyleManager) { 15 | super(null, true); 16 | this.mStyleManager = mStyleManager; 17 | } 18 | 19 | @Override 20 | public boolean commitText(CharSequence text, int newCursorPosition) { 21 | Log.d(TAG, "commitText: " + newCursorPosition + " count: " + text.length() + " -> " + text); 22 | if (text.length() == 0) { 23 | mStyleManager.commandDelete(true); 24 | } else { 25 | mStyleManager.commandInput(text, true); 26 | } 27 | return true; 28 | } 29 | 30 | /** 31 | * 当软键盘删除文本之前,会调用这个方法通知输入框,我们可以重写这个方法并判断是否要拦截这个删除事件。 32 | * 在谷歌输入法上,点击退格键的时候不会调用{@link #sendKeyEvent(KeyEvent event)}, 33 | * 而是直接回调这个方法,所以也要在这个方法上做拦截; 34 | **/ 35 | @Override 36 | public boolean deleteSurroundingText(int beforeLength, int afterLength) { 37 | mStyleManager.commandDelete(beforeLength - afterLength, true); 38 | //super.deleteSurroundingText(beforeLength, afterLength); 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { 44 | Log.e(TAG, "deleteSurroundingTextInCodePoints: " + (afterLength - beforeLength)); 45 | return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength); 46 | } 47 | 48 | /** 49 | * 当在软件盘上点击某些按钮(比如退格键,数字键,回车键等),该方法可能会被触发(取决于输入法的开发者), 50 | * 所以也可以重写该方法并拦截这些事件,这些事件就不会被分发到输入框了 51 | **/ 52 | @Override 53 | public boolean sendKeyEvent(KeyEvent event) { 54 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 55 | switch (event.getKeyCode()) { 56 | case KeyEvent.KEYCODE_DEL: 57 | mStyleManager.commandDelete(true); 58 | return true; 59 | case KeyEvent.KEYCODE_ENTER: 60 | mStyleManager.commandEnter(true); 61 | return true; 62 | default: 63 | Log.e(TAG, "sendKeyEvent: " + event.getKeyCode()); 64 | return super.sendKeyEvent(event); 65 | } 66 | } 67 | return super.sendKeyEvent(event); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/NoteEditorSelectionListener.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor; 2 | 3 | import com.jscheng.srich.model.Options; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/27 7 | */ 8 | public interface NoteEditorSelectionListener { 9 | void onStyleChange(int start, int end, Options options); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/NoteLineSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render; 2 | 3 | import android.text.Editable; 4 | import android.text.SpannableStringBuilder; 5 | import android.text.Spanned; 6 | import android.text.style.LeadingMarginSpan; 7 | import android.widget.EditText; 8 | 9 | import com.jscheng.srich.editor.NoteEditorConfig; 10 | import com.jscheng.srich.editor.NoteEditorRender; 11 | import com.jscheng.srich.model.Paragraph; 12 | 13 | /** 14 | * 绘制行样式 15 | * Created By Chengjunsen on 2019/3/4 16 | */ 17 | public abstract class NoteLineSpanRender { 18 | 19 | public void draw(int globalPos, int num, Paragraph paragraph, SpannableStringBuilder builder) { 20 | if (isLineStyle(paragraph) && paragraph.isPlaceHolder()) { 21 | String url = paragraph.getImageUrl(); 22 | int level = paragraph.getIndentation(); 23 | 24 | T span = createSpan(num, level, url); 25 | int start = globalPos; 26 | int end = 0; 27 | 28 | if (LeadingMarginSpan.class.isAssignableFrom(span.getClass())) { 29 | end = globalPos + paragraph.getLength(); 30 | } else { 31 | end = globalPos + NoteEditorConfig.PlaceHoldChar.length(); 32 | } 33 | builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 34 | } 35 | } 36 | 37 | protected abstract boolean isLineStyle(Paragraph paragraph); 38 | 39 | protected abstract T createSpan(int num, int level, String url); 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/NoteWordSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render; 2 | 3 | import android.text.Editable; 4 | import android.text.SpannableStringBuilder; 5 | import android.text.Spanned; 6 | import android.text.style.CharacterStyle; 7 | import android.widget.EditText; 8 | 9 | import com.jscheng.srich.model.Paragraph; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 绘制字体样式 15 | * Created By Chengjunsen on 2019/3/4 16 | */ 17 | public abstract class NoteWordSpanRender { 18 | 19 | public void draw(int globalPos, Paragraph paragraph, SpannableStringBuilder builder) { 20 | List wordStyles = paragraph.getWordStyles(); 21 | int start = -1; 22 | for (int i = 0; i < wordStyles.size(); i++) { 23 | if (isStyle(wordStyles.get(i))) { 24 | if (start == -1) { start = i; } 25 | } else if (start > -1) { 26 | draw(globalPos, start, i, builder); 27 | start = -1; 28 | } 29 | } 30 | if (start > -1) { 31 | draw(globalPos, start, wordStyles.size(), builder); 32 | } 33 | } 34 | 35 | private void draw(int globalPos, int start, int end, SpannableStringBuilder builder) { 36 | builder.setSpan(createSpan(), start + globalPos, end + globalPos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 37 | } 38 | 39 | protected abstract T createSpan(); 40 | 41 | protected abstract int getStyle(); 42 | 43 | protected abstract boolean isStyle(int style); 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteBulletLineSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteBulletSpan; 5 | import com.jscheng.srich.model.Paragraph; 6 | import com.jscheng.srich.model.Style; 7 | 8 | /** 9 | * Created By Chengjunsen on 2019/3/4 10 | */ 11 | public class NoteBulletLineSpanRender extends NoteLineSpanRender { 12 | 13 | public NoteBulletLineSpanRender() { 14 | } 15 | 16 | @Override 17 | protected boolean isLineStyle(Paragraph paragraph) { 18 | return paragraph.isBulletList(); 19 | } 20 | 21 | @Override 22 | protected Object createSpan(int num, int level, String url) { 23 | return NoteBulletSpan.create(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteCheckBoxSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.view.View; 6 | 7 | import com.jscheng.srich.R; 8 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 9 | import com.jscheng.srich.editor.spans.NoteCheckBoxSpan; 10 | import com.jscheng.srich.model.Paragraph; 11 | import com.jscheng.srich.model.Style; 12 | 13 | /** 14 | * Created By Chengjunsen on 2019/3/6 15 | */ 16 | public class NoteCheckBoxSpanRender extends NoteLineSpanRender { 17 | 18 | private View mView; 19 | private Bitmap mBitmap; 20 | 21 | public NoteCheckBoxSpanRender(View view) { 22 | this.mView = view; 23 | this.mBitmap = BitmapFactory.decodeResource(view.getContext().getResources(), R.mipmap.ic_note_edit_check); 24 | } 25 | 26 | @Override 27 | protected boolean isLineStyle(Paragraph paragraph) { 28 | return paragraph.isCheckbox(); 29 | } 30 | 31 | @Override 32 | protected NoteCheckBoxSpan createSpan(int num, int level, String url) { 33 | return NoteCheckBoxSpan.create(mBitmap); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteDividingSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import android.view.View; 4 | 5 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 6 | import com.jscheng.srich.editor.spans.NoteDividingLineSpan; 7 | import com.jscheng.srich.model.Paragraph; 8 | import com.jscheng.srich.model.Style; 9 | 10 | /** 11 | * Created By Chengjunsen on 2019/3/4 12 | */ 13 | public class NoteDividingSpanRender extends NoteLineSpanRender { 14 | private View view; 15 | 16 | public NoteDividingSpanRender(View view) { 17 | this.view = view; 18 | } 19 | 20 | @Override 21 | protected boolean isLineStyle(Paragraph paragraph) { 22 | return paragraph.isDividingLine(); 23 | } 24 | 25 | @Override 26 | protected NoteDividingLineSpan createSpan(int num, int level, String url) { 27 | int width = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight(); 28 | return NoteDividingLineSpan.create(width); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteImageSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.drawable.BitmapDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.view.View; 8 | 9 | import com.jscheng.srich.R; 10 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 11 | import com.jscheng.srich.editor.spans.NoteImageSpan; 12 | import com.jscheng.srich.image_loader.ImageLoader; 13 | import com.jscheng.srich.model.Paragraph; 14 | 15 | /** 16 | * Created By Chengjunsen on 2019/3/6 17 | */ 18 | public class NoteImageSpanRender extends NoteLineSpanRender { 19 | 20 | private int margin = 50; 21 | private View mView; 22 | 23 | public NoteImageSpanRender(View view) { 24 | this.mView = view; 25 | } 26 | 27 | @Override 28 | protected boolean isLineStyle(Paragraph paragraph) { 29 | return paragraph.isImage(); 30 | } 31 | 32 | @Override 33 | protected NoteImageSpan createSpan(int num, int level, String url) { 34 | int width = mView.getWidth() - mView.getPaddingLeft() - mView.getPaddingRight(); 35 | Bitmap bitmap = ImageLoader.with(mView.getContext()).get(url, width); 36 | if (bitmap == null) { 37 | bitmap = BitmapFactory.decodeResource(mView.getResources(), R.mipmap.ic_note_edit_loading); 38 | } 39 | 40 | int actualWidth = Math.min(width, bitmap.getWidth()); 41 | int actualHeight = (int) ((float) actualWidth / bitmap.getWidth() * bitmap.getHeight()); 42 | 43 | BitmapDrawable drawable = new BitmapDrawable(mView.getResources(), bitmap); 44 | drawable.setBounds(0, 0, actualWidth, actualHeight); 45 | 46 | return new NoteImageSpan(drawable); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteIndentationSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteIndentationSpan; 5 | import com.jscheng.srich.model.Paragraph; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/7 9 | */ 10 | public class NoteIndentationSpanRender extends NoteLineSpanRender { 11 | 12 | @Override 13 | protected boolean isLineStyle(Paragraph paragraph) { 14 | return paragraph.getIndentation() > 0; 15 | } 16 | 17 | @Override 18 | protected NoteIndentationSpan createSpan(int num, int level, String url) { 19 | return NoteIndentationSpan.create(level); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteNumSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import android.view.View; 4 | 5 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 6 | import com.jscheng.srich.editor.spans.NoteNumSpan; 7 | import com.jscheng.srich.model.Paragraph; 8 | import com.jscheng.srich.model.Style; 9 | import com.jscheng.srich.utils.DisplayUtil; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/3/6 13 | */ 14 | public class NoteNumSpanRender extends NoteLineSpanRender { 15 | 16 | public NoteNumSpanRender(){ 17 | } 18 | 19 | @Override 20 | protected boolean isLineStyle(Paragraph paragraph) { 21 | return paragraph.isNumList(); 22 | } 23 | 24 | @Override 25 | protected NoteNumSpan createSpan(int num, int level, String url) { 26 | return NoteNumSpan.create(num); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/line_render/NoteUncheckBoxSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.line_render; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.view.View; 6 | 7 | import com.jscheng.srich.R; 8 | import com.jscheng.srich.editor.render.NoteLineSpanRender; 9 | import com.jscheng.srich.editor.spans.NoteCheckBoxSpan; 10 | import com.jscheng.srich.model.Paragraph; 11 | import com.jscheng.srich.model.Style; 12 | 13 | /** 14 | * Created By Chengjunsen on 2019/3/6 15 | */ 16 | public class NoteUncheckBoxSpanRender extends NoteLineSpanRender { 17 | 18 | private View mView; 19 | private Bitmap mBitmap; 20 | 21 | public NoteUncheckBoxSpanRender(View view) { 22 | this.mView = view; 23 | this.mBitmap = BitmapFactory.decodeResource(view.getContext().getResources(), R.mipmap.ic_note_edit_uncheck); 24 | } 25 | 26 | @Override 27 | protected boolean isLineStyle(Paragraph paragraph) { 28 | return paragraph.isUnCheckbox(); 29 | } 30 | 31 | @Override 32 | protected NoteCheckBoxSpan createSpan(int num, int level, String url) { 33 | return NoteCheckBoxSpan.create(mBitmap); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteBackgroundSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteBackgroundSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteBackgroundSpanRender extends NoteWordSpanRender { 11 | @Override 12 | protected NoteBackgroundSpan createSpan() { 13 | return NoteBackgroundSpan.create(); 14 | } 15 | 16 | @Override 17 | protected int getStyle() { 18 | return Style.BackgroudColor; 19 | } 20 | 21 | @Override 22 | protected boolean isStyle(int style) { 23 | return Style.isWordStyle(style, getStyle()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteBoldSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteBoldSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteBoldSpanRender extends NoteWordSpanRender { 11 | 12 | @Override 13 | protected NoteBoldSpan createSpan() { 14 | return NoteBoldSpan.create(); 15 | } 16 | 17 | @Override 18 | protected int getStyle() { 19 | return Style.Bold; 20 | } 21 | 22 | @Override 23 | protected boolean isStyle(int style) { 24 | return Style.isWordStyle(style, getStyle()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteItalicSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteItalicSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteItalicSpanRender extends NoteWordSpanRender { 11 | 12 | @Override 13 | protected NoteItalicSpan createSpan() { 14 | return NoteItalicSpan.create(); 15 | } 16 | 17 | @Override 18 | protected int getStyle() { 19 | return Style.Italic; 20 | } 21 | 22 | @Override 23 | protected boolean isStyle(int style) { 24 | return Style.isWordStyle(style, getStyle()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteStrikethroughSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteStrikethroughSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteStrikethroughSpanRender extends NoteWordSpanRender { 11 | 12 | @Override 13 | protected NoteStrikethroughSpan createSpan() { 14 | return NoteStrikethroughSpan.create(); 15 | } 16 | 17 | @Override 18 | protected int getStyle() { 19 | return Style.Strikethrough; 20 | } 21 | 22 | @Override 23 | protected boolean isStyle(int style) { 24 | return Style.isWordStyle(style, getStyle()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteSubscriptSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteSubscriptSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteSubscriptSpanRender extends NoteWordSpanRender { 11 | @Override 12 | protected NoteSubscriptSpan createSpan() { 13 | return NoteSubscriptSpan.create(); 14 | } 15 | 16 | @Override 17 | protected int getStyle() { 18 | return Style.SubScript; 19 | } 20 | 21 | @Override 22 | protected boolean isStyle(int style) { 23 | return Style.isWordStyle(style, getStyle()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteSuperscriptSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteSuperscriptSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteSuperscriptSpanRender extends NoteWordSpanRender { 11 | @Override 12 | protected NoteSuperscriptSpan createSpan() { 13 | return NoteSuperscriptSpan.create(); 14 | } 15 | 16 | @Override 17 | protected int getStyle() { 18 | return Style.SuperScript; 19 | } 20 | 21 | @Override 22 | protected boolean isStyle(int style) { 23 | return Style.isWordStyle(style, getStyle()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/render/word_render/NoteUnderlineSpanRender.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.render.word_render; 2 | 3 | import com.jscheng.srich.editor.render.NoteWordSpanRender; 4 | import com.jscheng.srich.editor.spans.NoteUnderLineSpan; 5 | import com.jscheng.srich.model.Style; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/3/4 9 | */ 10 | public class NoteUnderlineSpanRender extends NoteWordSpanRender { 11 | @Override 12 | protected NoteUnderLineSpan createSpan() { 13 | return NoteUnderLineSpan.create(); 14 | } 15 | 16 | @Override 17 | protected int getStyle() { 18 | return Style.UnderLine; 19 | } 20 | 21 | @Override 22 | protected boolean isStyle(int style) { 23 | return Style.isWordStyle(style, getStyle()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteBackgroundSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Color; 4 | import android.text.style.BackgroundColorSpan; 5 | 6 | import com.jscheng.srich.editor.NoteEditorConfig; 7 | 8 | /** 9 | * Created By Chengjunsen on 2019/2/25 10 | */ 11 | public class NoteBackgroundSpan extends BackgroundColorSpan{ 12 | 13 | private final static int color = NoteEditorConfig.HighLightBackgroundColor; 14 | 15 | public NoteBackgroundSpan() { 16 | super(color); 17 | } 18 | 19 | public static NoteBackgroundSpan create() { 20 | return new NoteBackgroundSpan(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteBoldSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Typeface; 4 | import android.text.style.StyleSpan; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/2/25 8 | */ 9 | public class NoteBoldSpan extends StyleSpan { 10 | 11 | public NoteBoldSpan() { 12 | super(Typeface.BOLD); 13 | } 14 | 15 | public static NoteBoldSpan create() { 16 | return new NoteBoldSpan(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteBulletSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.text.Layout; 9 | import android.text.style.BulletSpan; 10 | import android.text.style.LeadingMarginSpan; 11 | import android.text.style.ReplacementSpan; 12 | 13 | /** 14 | * Created By Chengjunsen on 2019/2/25 15 | */ 16 | public class NoteBulletSpan extends BulletSpan { 17 | 18 | private int mRadius = 5; 19 | private int mMargin = 25; 20 | 21 | public static NoteBulletSpan create() { 22 | return new NoteBulletSpan(); 23 | } 24 | 25 | @Override 26 | public int getLeadingMargin(boolean first) { 27 | return (mMargin + mRadius) * 2; 28 | } 29 | 30 | @Override 31 | public void drawLeadingMargin(Canvas canvas, Paint paint, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { 32 | if (first) { 33 | Paint.Style style = paint.getStyle(); 34 | paint.setStyle(Paint.Style.FILL); 35 | paint.setColor(Color.BLACK); 36 | 37 | int transY = top + (bottom - top) / 2 - mRadius; 38 | int transX = x + dir + mMargin; 39 | 40 | canvas.save(); 41 | canvas.translate(transX, transY); 42 | canvas.drawCircle(0, 0, mRadius, paint); 43 | canvas.restore(); 44 | 45 | paint.setStyle(style); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteCheckBoxSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.text.Layout; 11 | import android.text.Spanned; 12 | import android.text.style.LeadingMarginSpan; 13 | import android.text.style.ReplacementSpan; 14 | 15 | /** 16 | * Created By Chengjunsen on 2019/3/6 17 | */ 18 | public class NoteCheckBoxSpan implements NoteClickSpan, LeadingMarginSpan { 19 | private Bitmap mBitmap; 20 | private int mMargin = 12; 21 | private int mWidth = 50; 22 | 23 | 24 | public NoteCheckBoxSpan(Bitmap bitmap) { 25 | mBitmap = bitmap; 26 | } 27 | 28 | @Override 29 | public int getLeadingMargin(boolean first) { 30 | return mWidth + mMargin; 31 | } 32 | 33 | @Override 34 | public void drawLeadingMargin(Canvas canvas, Paint paint, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { 35 | if (((Spanned)text).getSpanStart(this) == start) { 36 | paint.setColor(Color.BLACK); 37 | Paint.Style style = paint.getStyle(); 38 | paint.setStyle(Paint.Style.FILL); 39 | int transX = x + dir; 40 | int transY = top; 41 | canvas.save(); 42 | canvas.translate(transX, transY); 43 | Rect resRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 44 | Rect destrect = new Rect(0, 0, mWidth, mWidth); 45 | canvas.drawBitmap(mBitmap, resRect, destrect, paint); 46 | canvas.restore(); 47 | paint.setStyle(style); 48 | } 49 | } 50 | 51 | public static NoteCheckBoxSpan create(Bitmap bitmap) { 52 | return new NoteCheckBoxSpan(bitmap); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteClickSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/6 5 | */ 6 | public interface NoteClickSpan { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteDividingLineSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.text.Layout; 9 | import android.text.style.AlignmentSpan; 10 | import android.text.style.ReplacementSpan; 11 | 12 | /** 13 | * Created By Chengjunsen on 2019/2/25 14 | */ 15 | public class NoteDividingLineSpan extends ReplacementSpan implements AlignmentSpan { 16 | 17 | private int mlineWidth; 18 | private int mMargin; 19 | 20 | public NoteDividingLineSpan(int width) { 21 | this.mMargin = 50; 22 | this.mlineWidth = width - 2 * mMargin; 23 | } 24 | 25 | @Override 26 | public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) { 27 | return mlineWidth; 28 | } 29 | 30 | @Override 31 | public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { 32 | paint.setColor(Color.BLACK); 33 | paint.setStrokeWidth(2); 34 | canvas.drawLine(x , y, x + mlineWidth, y, paint); 35 | } 36 | 37 | public static NoteDividingLineSpan create(int mWidth) { 38 | return new NoteDividingLineSpan(mWidth); 39 | } 40 | 41 | @Override 42 | public Layout.Alignment getAlignment() { 43 | return Layout.Alignment.ALIGN_CENTER; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteImageSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | import android.graphics.Canvas; 3 | import android.graphics.Paint; 4 | import android.graphics.Rect; 5 | import android.graphics.drawable.BitmapDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.support.annotation.IntRange; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.text.Layout; 11 | import android.text.style.AlignmentSpan; 12 | import android.text.style.ImageSpan; 13 | 14 | /** 15 | * Created By Chengjunsen on 2019/3/6 16 | */ 17 | public class NoteImageSpan extends ImageSpan implements AlignmentSpan, NoteClickSpan{ 18 | 19 | private int margin = 30; 20 | 21 | public NoteImageSpan(BitmapDrawable drawable) { 22 | super(drawable, ALIGN_BASELINE); 23 | } 24 | 25 | @Override 26 | public Layout.Alignment getAlignment() { 27 | return Layout.Alignment.ALIGN_CENTER; 28 | } 29 | 30 | @Override 31 | public int getSize(@NonNull Paint paint, CharSequence text, 32 | @IntRange(from = 0) int start, @IntRange(from = 0) int end, 33 | @Nullable Paint.FontMetricsInt fm) { 34 | Drawable d = getDrawable(); 35 | Rect rect = d.getBounds(); 36 | 37 | if (fm != null) { 38 | fm.ascent = -rect.bottom - margin * 2; 39 | fm.descent = 0; 40 | 41 | fm.top = fm.ascent; 42 | fm.bottom = 0; 43 | } 44 | 45 | return rect.right; 46 | } 47 | 48 | @Override 49 | public void draw(@NonNull Canvas canvas, CharSequence text, 50 | @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, 51 | int top, int y, int bottom, @NonNull Paint paint) { 52 | BitmapDrawable d = (BitmapDrawable)getDrawable(); 53 | Rect rect = d.getBounds(); 54 | canvas.save(); 55 | 56 | int transY = bottom - rect.bottom - margin; 57 | if (mVerticalAlignment == ALIGN_BASELINE) { 58 | transY -= paint.getFontMetricsInt().descent; 59 | } 60 | 61 | canvas.translate(x, transY); 62 | d.draw(canvas); 63 | canvas.restore(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteIndentationSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.text.Layout; 7 | import android.text.style.LeadingMarginSpan; 8 | 9 | /** 10 | * Created By Chengjunsen on 2019/3/7 11 | */ 12 | public class NoteIndentationSpan implements LeadingMarginSpan { 13 | 14 | private int mLevel; 15 | private int mMargin; 16 | 17 | public NoteIndentationSpan(int level) { 18 | this.mLevel = level; 19 | this.mMargin = 50; 20 | } 21 | 22 | public static NoteIndentationSpan create(int level) { 23 | return new NoteIndentationSpan(level); 24 | } 25 | 26 | @Override 27 | public int getLeadingMargin(boolean first) { 28 | return mMargin * mLevel; 29 | } 30 | 31 | @Override 32 | public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteItalicSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Typeface; 4 | import android.text.style.StyleSpan; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/2/25 8 | */ 9 | public class NoteItalicSpan extends StyleSpan{ 10 | 11 | public NoteItalicSpan() { 12 | super(Typeface.ITALIC); 13 | } 14 | 15 | public static NoteItalicSpan create() { 16 | return new NoteItalicSpan(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteNumSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.text.Layout; 7 | import android.text.Spanned; 8 | import android.text.style.LeadingMarginSpan; 9 | 10 | /** 11 | * Created By Chengjunsen on 2019/3/6 12 | */ 13 | public class NoteNumSpan implements LeadingMarginSpan { 14 | private int mNum; 15 | private int mMargin = 15; 16 | private int mWidth = 40; 17 | 18 | public NoteNumSpan(int num) { 19 | this.mNum = num; 20 | } 21 | 22 | public static NoteNumSpan create(int num) { 23 | return new NoteNumSpan(num); 24 | } 25 | 26 | @Override 27 | public int getLeadingMargin(boolean first) { 28 | return mWidth + mMargin * 2; 29 | } 30 | 31 | @Override 32 | public void drawLeadingMargin(Canvas canvas, Paint paint, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) { 33 | if (((Spanned)text).getSpanStart(this) == start) { 34 | paint.setColor(Color.BLACK); 35 | Paint.Style style = paint.getStyle(); 36 | paint.setStyle(Paint.Style.FILL); 37 | int transX = x + dir + mMargin; 38 | int transY = baseline; 39 | canvas.save(); 40 | canvas.translate(transX, transY); 41 | canvas.drawText(String.valueOf(mNum) + ".", 0, 0, paint); 42 | canvas.restore(); 43 | paint.setStyle(style); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteStrikethroughSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.text.style.StrikethroughSpan; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/25 7 | */ 8 | public class NoteStrikethroughSpan extends StrikethroughSpan { 9 | 10 | public static NoteStrikethroughSpan create() { 11 | return new NoteStrikethroughSpan(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteSubscriptSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/25 5 | * 下标 6 | */ 7 | public class NoteSubscriptSpan extends android.text.style.SubscriptSpan { 8 | 9 | public static NoteSubscriptSpan create() { 10 | return new NoteSubscriptSpan(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteSuperscriptSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/25 5 | * 下标 6 | */ 7 | public class NoteSuperscriptSpan extends android.text.style.SuperscriptSpan { 8 | 9 | public static NoteSuperscriptSpan create() { 10 | return new NoteSuperscriptSpan(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteTypefaceSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.text.style.TypefaceSpan; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/28 7 | */ 8 | public class NoteTypefaceSpan extends TypefaceSpan { 9 | 10 | public NoteTypefaceSpan(String family) { 11 | super(family); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteUnderLineSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.text.style.UnderlineSpan; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/25 7 | */ 8 | public class NoteUnderLineSpan extends UnderlineSpan { 9 | public static NoteUnderLineSpan create() { 10 | return new NoteUnderLineSpan(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/editor/spans/NoteUriSpan.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.editor.spans; 2 | 3 | import android.text.style.URLSpan; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/25 7 | */ 8 | public class NoteUriSpan extends URLSpan{ 9 | 10 | public NoteUriSpan(String url) { 11 | super(url); 12 | } 13 | 14 | public static NoteUriSpan create(String url) { 15 | return new NoteUriSpan(url); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/DownSampler.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.support.media.ExifInterface; 6 | import android.util.Size; 7 | 8 | import java.io.BufferedInputStream; 9 | import java.io.IOException; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/3/21 13 | */ 14 | public class DownSampler { 15 | 16 | private static final int MARK_POSITION = 10 * 1024 * 1024; 17 | 18 | public static Size getDimensions(BufferedInputStream is, BitmapFactory.Options options) { 19 | options.inJustDecodeBounds = true; 20 | decodeStream(is, options); 21 | options.inJustDecodeBounds = false; 22 | return new Size(options.outWidth, options.outHeight); 23 | } 24 | 25 | public static boolean isScaling(BitmapFactory.Options options) { 26 | return options.inTargetDensity > 0 && options.inDensity > 0 27 | && options.inTargetDensity != options.inDensity; 28 | } 29 | 30 | public static Bitmap decodeStream(BufferedInputStream is, BitmapFactory.Options options) { 31 | if (options.inJustDecodeBounds) { 32 | is.mark(MARK_POSITION); 33 | } 34 | final Bitmap result = BitmapFactory.decodeStream(is, null, options); 35 | if (options.inJustDecodeBounds) { 36 | try { 37 | is.reset(); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | return result; 43 | } 44 | 45 | public static int getRotate(BufferedInputStream is) { 46 | try { 47 | int orientation = getOrientation(is); 48 | return getExifOrientationDegrees(orientation); 49 | } catch (IOException e) { 50 | e.printStackTrace(); 51 | } 52 | return 0; 53 | } 54 | 55 | public static int getOrientation(BufferedInputStream is) throws IOException { 56 | ExifInterface exifInterface = null; 57 | is.mark(MARK_POSITION); 58 | exifInterface = new ExifInterface(is); 59 | int result = exifInterface.getAttributeInt( 60 | ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 61 | try { 62 | is.reset(); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } 66 | if (result == ExifInterface.ORIENTATION_UNDEFINED) { 67 | return 0; 68 | } 69 | return result; 70 | } 71 | 72 | public static int getExifOrientationDegrees(int exifOrientation) { 73 | final int degreesToRotate; 74 | switch (exifOrientation) { 75 | case ExifInterface.ORIENTATION_TRANSPOSE: 76 | case ExifInterface.ORIENTATION_ROTATE_90: 77 | degreesToRotate = 90; 78 | break; 79 | case ExifInterface.ORIENTATION_ROTATE_180: 80 | case ExifInterface.ORIENTATION_FLIP_VERTICAL: 81 | degreesToRotate = 180; 82 | break; 83 | case ExifInterface.ORIENTATION_TRANSVERSE: 84 | case ExifInterface.ORIENTATION_ROTATE_270: 85 | degreesToRotate = 270; 86 | break; 87 | default: 88 | degreesToRotate = 0; 89 | break; 90 | } 91 | return degreesToRotate; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/FileImageJob.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.text.TextUtils; 6 | import java.io.BufferedInputStream; 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.FileNotFoundException; 10 | 11 | import com.jscheng.srich.utils.UriPathUtil; 12 | 13 | /** 14 | * Created By Chengjunsen on 2019/3/22 15 | */ 16 | public class FileImageJob extends ImageJob { 17 | 18 | public FileImageJob(Context context, String key, String url, ImageJobCallback callback, ImageDiskCache diskCache) { 19 | super(context, key, url, callback, diskCache); 20 | } 21 | 22 | @Override 23 | public void run() { 24 | String path = null; 25 | if (isContentUrl(url)) { 26 | path = UriPathUtil.getAbsulotePath(context, Uri.parse(url)); 27 | } else if (isFileUrl(url)){ 28 | path = url; 29 | } 30 | if (TextUtils.isEmpty(path)) { 31 | failed("url is not valid"); 32 | } else { 33 | try { 34 | BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(path)); 35 | successed(inputStream); 36 | } catch (FileNotFoundException e) { 37 | e.printStackTrace(); 38 | failed(e.toString()); 39 | } 40 | } 41 | } 42 | 43 | private boolean isFileUrl(String url) { 44 | return new File(url).exists(); 45 | } 46 | 47 | private boolean isContentUrl(String url) { 48 | return url.toLowerCase().startsWith("content"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/HttpImageJob.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.content.Context; 4 | 5 | import com.jscheng.srich.utils.StorageUtil; 6 | 7 | import java.io.BufferedInputStream; 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | import okhttp3.Cache; 12 | import okhttp3.Call; 13 | import okhttp3.OkHttpClient; 14 | import okhttp3.Request; 15 | import okhttp3.Response; 16 | 17 | /** 18 | * Created By Chengjunsen on 2019/3/22 19 | */ 20 | public class HttpImageJob extends ImageJob { 21 | /** 22 | * okhttp 23 | */ 24 | private static OkHttpClient mOkhttpClient; 25 | /** 26 | * okhttp 文件名字 27 | */ 28 | private static final String OkhttpCacheDirName = "okhttp"; 29 | 30 | public HttpImageJob(Context context, String key, String url, ImageJobCallback callback, ImageDiskCache diskCache) { 31 | super(context, key, url, callback, diskCache); 32 | 33 | int mOkhttpCacheSize = 10 * 1024 * 1024; 34 | File okhttpCacheFile = getDiskCachePath(context, OkhttpCacheDirName); 35 | mOkhttpClient = new OkHttpClient.Builder() 36 | .cache(new Cache(okhttpCacheFile, mOkhttpCacheSize)) 37 | .build(); 38 | } 39 | 40 | @Override 41 | public void run() { 42 | try { 43 | Request bitmapRequest = new Request.Builder().get().url(url).build(); 44 | Call call = mOkhttpClient.newCall(bitmapRequest); 45 | Response response = call.execute(); 46 | if (response.isSuccessful()) { 47 | BufferedInputStream inputStream = new BufferedInputStream(response.body().byteStream()); 48 | successed(inputStream); 49 | } else { 50 | failed(response.message()); 51 | } 52 | } catch (IOException e) { 53 | e.printStackTrace(); 54 | failed(e.toString()); 55 | } 56 | } 57 | 58 | private File getDiskCachePath(Context context, String uniqueName){ 59 | String cachePath = StorageUtil.getDiskCachePath(context); 60 | return new File( cachePath + File.separator +uniqueName); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageFetcher.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/3/21 13 | */ 14 | class ImageFetcher implements ImageJobCallback{ 15 | private static final String TAG = "TAG"; 16 | 17 | /** 18 | * 失败最大重试次数 19 | */ 20 | private static final int MaxUrlFailedTime = 2; 21 | 22 | /** 23 | * 正在请求下载的url集合 24 | */ 25 | private volatile static List mRequestingUrls; 26 | 27 | /** 28 | * 失败的URL的key和失败次数 29 | */ 30 | private static HashMap mFailedUrls; 31 | 32 | /** 33 | * 线程池 34 | */ 35 | private static ExecutorService mExecutors; 36 | 37 | private ImageDiskCache mDiskCache; 38 | 39 | private Context mAppContext; 40 | 41 | private ImageFetcherCallback mCallback; 42 | 43 | public ImageFetcher(Context context, ImageDiskCache cache, ImageFetcherCallback callback) { 44 | mDiskCache = cache; 45 | mAppContext = context; 46 | mCallback = callback; 47 | mRequestingUrls = new ArrayList<>(); 48 | mFailedUrls = new HashMap<>(); 49 | mExecutors = Executors.newFixedThreadPool(3); 50 | } 51 | 52 | public synchronized void load(String url, String key) { 53 | if (mRequestingUrls.contains(url)) { 54 | Log.e(TAG, "load: url is requesting"); 55 | return; 56 | } 57 | if (mFailedUrls.containsKey(key) && mFailedUrls.get(key) > MaxUrlFailedTime) { 58 | onJobFailed(url, key, "load: url is failed max time"); 59 | return; 60 | } 61 | mRequestingUrls.add(url); 62 | ImageJob job = ImageJobFactory.build(mAppContext, url, key, this, mDiskCache); 63 | mExecutors.submit(job); 64 | } 65 | 66 | @Override 67 | public synchronized void onJobFailed(final String url, final String key, final String err) { 68 | mRequestingUrls.remove(url); 69 | Integer time = mFailedUrls.get(key); 70 | time = time == null ? 1 : time + 1; 71 | mFailedUrls.put(key, time); 72 | 73 | mCallback.onFetchFailed(url, key, err); 74 | } 75 | 76 | @Override 77 | public synchronized void onJobSuccess(final String url, final String key) { 78 | mRequestingUrls.remove(url); 79 | 80 | mCallback.onFetchSuccess(url, key); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageFetcherCallback.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/22 5 | */ 6 | public interface ImageFetcherCallback { 7 | void onFetchFailed(final String url, final String key, final String err); 8 | void onFetchSuccess(final String url, final String key); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageGlobalListener.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/22 5 | */ 6 | public interface ImageGlobalListener { 7 | void onImageLoadSuccess(String url); 8 | void onImageLoadFailed(String url, String err); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageJob.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.util.Size; 7 | 8 | import java.io.BufferedInputStream; 9 | 10 | /** 11 | * Created By Chengjunsen on 2019/3/22 12 | */ 13 | public abstract class ImageJob implements Runnable{ 14 | 15 | protected String key; 16 | 17 | protected String url; 18 | 19 | protected Context context; 20 | 21 | private ImageJobCallback callback; 22 | 23 | private ImageDiskCache mDiskCache; 24 | 25 | public ImageJob(Context context, String key, String url, ImageJobCallback callback, ImageDiskCache diskCache) { 26 | this.key = key; 27 | this.url = url; 28 | this.callback = callback; 29 | this.mDiskCache = diskCache; 30 | this.context = context; 31 | } 32 | 33 | protected void successed(BufferedInputStream inputStream) { 34 | Size size = DownSampler.getDimensions(inputStream, new BitmapFactory.Options()); 35 | mDiskCache.put(inputStream, key); 36 | Bitmap bitmap = mDiskCache.get(key, 0); 37 | callback.onJobSuccess(url, key); 38 | } 39 | 40 | protected void failed(String err) { 41 | callback.onJobFailed(url, key, err); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageJobCallback.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/22 5 | */ 6 | public interface ImageJobCallback { 7 | void onJobFailed(final String url, final String key, final String err); 8 | void onJobSuccess(final String url, final String key); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageJobFactory.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/3/22 7 | * 简单的工厂方法 8 | */ 9 | public class ImageJobFactory { 10 | 11 | public static ImageJob build(Context context, String url, String key, ImageJobCallback callback, ImageDiskCache mCache) { 12 | if (url.startsWith("http")) { 13 | return new HttpImageJob(context, key, url, callback, mCache); 14 | } else { 15 | return new FileImageJob(context, key, url, callback, mCache); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageKeyFactory.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import com.jscheng.srich.utils.MdUtil; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/3/21 7 | */ 8 | public class ImageKeyFactory { 9 | 10 | public static String generateKey(String url) { 11 | return MdUtil.encode(url); 12 | } 13 | 14 | public static String generateKey(String url, int width) { 15 | return MdUtil.encode(url + width); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageMemoryCache.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.LruCache; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/3/21 8 | */ 9 | public class ImageMemoryCache { 10 | /** 11 | * 最大内存缓存空间 12 | */ 13 | private static final int mMemoryCacheSize = (int)Runtime.getRuntime().maxMemory() / 8; 14 | /** 15 | * 内存图片缓存 16 | */ 17 | private static LruCache mMemoryCache; 18 | 19 | public ImageMemoryCache() { 20 | mMemoryCache = new LruCache(mMemoryCacheSize) { 21 | @Override 22 | protected int sizeOf(String key, Bitmap value) { 23 | return value.getByteCount(); 24 | } 25 | }; 26 | } 27 | 28 | public synchronized Bitmap get(String key, int width) { 29 | return mMemoryCache.get(key + width); 30 | } 31 | 32 | public synchronized void remove(String key, int width) { 33 | mMemoryCache.remove(key + width); 34 | } 35 | 36 | public synchronized void put(String key, int width, Bitmap bitmap) { 37 | mMemoryCache.put(key + width, bitmap); 38 | } 39 | 40 | public synchronized boolean isCache(String key, int width) { 41 | return mMemoryCache.get(key + width) != null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageTarget.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/21 5 | */ 6 | public interface ImageTarget { 7 | 8 | void onResourceReady(String url, String key); 9 | 10 | void onResourceFailed(String url, String key, String err); 11 | 12 | String getUrl(); 13 | 14 | int getMaxWidth(); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_loader/ImageViewTarget.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_loader; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.Log; 5 | import android.view.ViewTreeObserver; 6 | import android.widget.ImageView; 7 | import java.lang.ref.WeakReference; 8 | 9 | /** 10 | * Created By Chengjunsen on 2019/3/21 11 | */ 12 | public class ImageViewTarget implements ImageTarget { 13 | private WeakReference mImageView; 14 | private String url; 15 | private String key; 16 | 17 | public ImageViewTarget(ImageView imageView, String key, String url) { 18 | imageView.setTag(key); 19 | this.mImageView = new WeakReference(imageView) ; 20 | this.key = key; 21 | this.url = url; 22 | } 23 | 24 | @Override 25 | public void onResourceReady(final String url, final String key) { 26 | final ImageView imageView = mImageView.get(); 27 | if (imageView == null) { 28 | return; 29 | } 30 | if (imageView.getMeasuredWidth() > 0) { 31 | setResource(url, key); 32 | } else { 33 | final ViewTreeObserver observer = imageView.getViewTreeObserver(); 34 | observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 35 | @Override 36 | public boolean onPreDraw() { 37 | setResource(url, key); 38 | imageView.getViewTreeObserver().removeOnPreDrawListener(this); 39 | return false; 40 | } 41 | }); 42 | } 43 | } 44 | 45 | private void setResource(String url, String key) { 46 | final ImageView imageView = mImageView.get(); 47 | if (imageView == null) { 48 | return; 49 | } 50 | Bitmap bitmap = ImageLoader.with(imageView.getContext()).getCache(url, imageView.getMeasuredWidth()); 51 | if (bitmap != null) { 52 | imageView.setImageBitmap(bitmap); 53 | } 54 | } 55 | 56 | @Override 57 | public void onResourceFailed(String url, String key, String err) { 58 | Log.e("TAG", "loadBitmap faied: " + url + " " + err); 59 | } 60 | 61 | @Override 62 | public String getUrl() { 63 | return url; 64 | } 65 | 66 | @Override 67 | public int getMaxWidth() { 68 | final ImageView imageView = mImageView.get(); 69 | if (imageView != null) { 70 | return imageView.getMeasuredWidth(); 71 | } 72 | return 0; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/image_preview/ImagePreviewActivity.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.image_preview; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.view.WindowManager; 7 | 8 | import com.jscheng.annotations.Route; 9 | import com.jscheng.srich.BaseActivity; 10 | import com.jscheng.srich.R; 11 | import com.jscheng.srich.image_loader.ImageLoader; 12 | import com.jscheng.srich.widget.PinchImageView; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * 图片预览 18 | * Created By Chengjunsen on 2019/3/15 19 | */ 20 | @Route("imagePreview") 21 | public class ImagePreviewActivity extends BaseActivity { 22 | 23 | PinchImageView mImageView; 24 | 25 | @Override 26 | protected void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_image_preview); 29 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 30 | getWindow().setStatusBarColor(getColor(R.color.image_preview_background_color)); 31 | 32 | mImageView = findViewById(R.id.preview_imageview); 33 | mImageView.setImageResource(R.mipmap.ic_note_edit_loading); 34 | loadImage(); 35 | } 36 | 37 | private void loadImage() { 38 | Intent intent = getIntent(); 39 | List urls = intent.getStringArrayListExtra("urls"); 40 | int index = intent.getIntExtra("index", 0); 41 | if (urls.isEmpty() || index >= urls.size()) { 42 | return; 43 | } 44 | String url = urls.get(index); 45 | ImageLoader.with(this).load(url, mImageView); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/Note.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/2/20 8 | */ 9 | public class Note { 10 | private String id; 11 | 12 | private String title; 13 | 14 | private long createTime; 15 | 16 | private long modifyTime; 17 | 18 | private String summary; 19 | 20 | private String summaryImageUrl; 21 | 22 | private String localPath; 23 | 24 | private boolean isDirty; 25 | 26 | private List paragraphs; 27 | 28 | public Note() { 29 | paragraphs = new ArrayList<>(); 30 | isDirty = true; 31 | } 32 | 33 | public List getParagraphs() { 34 | return paragraphs; 35 | } 36 | 37 | public void setParagraphs(List paragraphs) { 38 | if (paragraphs != null) { 39 | this.paragraphs = paragraphs; 40 | } 41 | } 42 | 43 | public String getId() { 44 | return id; 45 | } 46 | 47 | public void setId(String id) { 48 | this.id = id; 49 | } 50 | 51 | public String getTitle() { 52 | return title; 53 | } 54 | 55 | public void setTitle(String title) { 56 | this.title = title; 57 | } 58 | 59 | public long getCreateTime() { 60 | return createTime; 61 | } 62 | 63 | public void setCreateTime(long createTime) { 64 | this.createTime = createTime; 65 | } 66 | 67 | public long getModifyTime() { 68 | return modifyTime; 69 | } 70 | 71 | public void setModifyTime(long modifyTime) { 72 | this.modifyTime = modifyTime; 73 | } 74 | 75 | public String getSummary() { 76 | return summary; 77 | } 78 | 79 | public void setSummary(String summary) { 80 | this.summary = summary; 81 | } 82 | 83 | public String getSummaryImageUrl() { 84 | return summaryImageUrl; 85 | } 86 | 87 | public void setSummaryImageUrl(String summaryImageUrl) { 88 | this.summaryImageUrl = summaryImageUrl; 89 | } 90 | 91 | public String getLocalPath() { 92 | return localPath; 93 | } 94 | 95 | public void setLocalPath(String localPath) { 96 | this.localPath = localPath; 97 | } 98 | 99 | public boolean isDirty() { 100 | return isDirty; 101 | } 102 | 103 | public void setDirty(boolean dirty) { 104 | isDirty = dirty; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/NoteBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/13 5 | */ 6 | public class NoteBuilder { 7 | private Note note; 8 | 9 | public NoteBuilder() { 10 | this.note = new Note(); 11 | } 12 | 13 | public Note build() { 14 | return note; 15 | } 16 | 17 | public NoteBuilder id(String id) { 18 | note.setId(id); 19 | return this; 20 | } 21 | 22 | public NoteBuilder title(String title) { 23 | note.setTitle(title); 24 | return this; 25 | } 26 | 27 | public NoteBuilder createtime(long time) { 28 | note.setCreateTime(time); 29 | return this; 30 | } 31 | 32 | public NoteBuilder motifytime(long time) { 33 | note.setModifyTime(time); 34 | return this; 35 | } 36 | 37 | public NoteBuilder summary(String summary) { 38 | note.setSummary(summary); 39 | return this; 40 | } 41 | 42 | public NoteBuilder summaryImageUrl(String url) { 43 | note.setSummaryImageUrl(url); 44 | return this; 45 | } 46 | 47 | public NoteBuilder localPath(String path) { 48 | note.setLocalPath(path); 49 | return this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/NoteSnap.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Note的快照,用于撤销、反撤销 7 | * Created By Chengjunsen on 2019/3/8 8 | */ 9 | public class NoteSnap { 10 | private String id; 11 | 12 | private long time; 13 | 14 | private List paragraphs; 15 | 16 | private int selectionStart; 17 | 18 | private int selectionEnd; 19 | 20 | private boolean isContinuousAction; 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public void setId(String id) { 27 | this.id = id; 28 | } 29 | 30 | public long getTime() { 31 | return time; 32 | } 33 | 34 | public void setTime(long time) { 35 | this.time = time; 36 | } 37 | 38 | public List getParagraphs() { 39 | return paragraphs; 40 | } 41 | 42 | public void setParagraphs(List paragraphs) { 43 | this.paragraphs = paragraphs; 44 | } 45 | 46 | public int getSelectionStart() { 47 | return selectionStart; 48 | } 49 | 50 | public void setSelectionStart(int selectionStart) { 51 | this.selectionStart = selectionStart; 52 | } 53 | 54 | public int getSelectionEnd() { 55 | return selectionEnd; 56 | } 57 | 58 | public void setSelectionEnd(int selectionEnd) { 59 | this.selectionEnd = selectionEnd; 60 | } 61 | 62 | public void setSelection(int start, int end) { 63 | this.selectionStart = start; 64 | this.selectionEnd = end; 65 | } 66 | 67 | public void setContinuousAction(boolean isContinuousAction) { 68 | this.isContinuousAction = true; 69 | } 70 | 71 | public boolean isContinuousAction() { 72 | return isContinuousAction; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/NoteSnapBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/3/18 8 | */ 9 | public class NoteSnapBuilder { 10 | 11 | private NoteSnap snap; 12 | 13 | public NoteSnapBuilder(Note note) { 14 | snap = new NoteSnap(); 15 | snap.setId(note.getId()); 16 | 17 | List list = new ArrayList<>(); 18 | for (Paragraph item: note.getParagraphs()) { 19 | list.add(item.clone()); 20 | } 21 | snap.setParagraphs(list); 22 | } 23 | 24 | public NoteSnapBuilder selection(int start, int end) { 25 | snap.setSelection(start, end); 26 | return this; 27 | } 28 | 29 | public NoteSnap build() { 30 | snap.setTime(System.currentTimeMillis()); 31 | return snap; 32 | } 33 | 34 | public NoteSnapBuilder continuous(boolean continuousAction) { 35 | snap.setContinuousAction(true); 36 | return this; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/OutLine.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/21 5 | */ 6 | public class OutLine { 7 | public enum Type { 8 | Note, Date 9 | } 10 | 11 | private Note note; 12 | 13 | private long time; 14 | 15 | private Type type; 16 | 17 | public OutLine(Note note) { 18 | this.type = Type.Note; 19 | this.note = note; 20 | } 21 | 22 | public OutLine(long time) { 23 | this.type = Type.Date; 24 | this.time = time; 25 | } 26 | 27 | public Note getNote() { 28 | return note; 29 | } 30 | 31 | public void setNote(Note note) { 32 | this.note = note; 33 | } 34 | 35 | public long getTime() { 36 | return time; 37 | } 38 | 39 | public void setTime(long time) { 40 | this.time = time; 41 | } 42 | 43 | public Type getType() { 44 | return type; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/ParagraphBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | import java.util.List; /** 4 | * Created By Chengjunsen on 2019/3/13 5 | */ 6 | public class ParagraphBuilder { 7 | 8 | private Paragraph paragraph; 9 | 10 | public ParagraphBuilder() { 11 | paragraph = new Paragraph(); 12 | paragraph.setDirty(true); 13 | } 14 | 15 | public ParagraphBuilder lineStyle(int lineStyle) { 16 | paragraph.setLineStyle(lineStyle); 17 | if (paragraph.isCheckbox()) { 18 | paragraph.setUnCheckbox(true); 19 | } 20 | return this; 21 | } 22 | 23 | public Paragraph build() { 24 | if (paragraph.isHeadStyle() || paragraph.isParagraphStyle()) { 25 | paragraph.insertPlaceHolder(); 26 | } else { 27 | paragraph.removePlaceHolder(); 28 | } 29 | return paragraph; 30 | } 31 | 32 | public ParagraphBuilder indentation(int indentation) { 33 | paragraph.setIndentation(indentation); 34 | return this; 35 | } 36 | 37 | public ParagraphBuilder dividingLine(boolean dividingLine) { 38 | paragraph.setDividingLine(dividingLine); 39 | return this; 40 | } 41 | 42 | public ParagraphBuilder image(String url) { 43 | paragraph.setImage(url); 44 | paragraph.setImage(true); 45 | return this; 46 | } 47 | 48 | public ParagraphBuilder image(boolean b) { 49 | paragraph.setImage(b); 50 | return this; 51 | } 52 | 53 | public ParagraphBuilder bullet(boolean b) { 54 | paragraph.setBulletList(b); 55 | return this; 56 | } 57 | 58 | public ParagraphBuilder addWords(String words, List wordStyles) { 59 | paragraph.addWords(words, wordStyles); 60 | return this; 61 | } 62 | 63 | public ParagraphBuilder numList(boolean b) { 64 | paragraph.setNumList(b); 65 | return this; 66 | } 67 | 68 | public ParagraphBuilder uncheckBox(boolean b) { 69 | paragraph.setUnCheckbox(b); 70 | return this; 71 | } 72 | 73 | public ParagraphBuilder checkbox(boolean b) { 74 | paragraph.setCheckbox(b); 75 | return this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/model/Style.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.model; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/1 5 | */ 6 | public class Style { 7 | public static final int Bold = 1; 8 | 9 | public static final int Italic = 2; 10 | 11 | public static final int UnderLine = 4; 12 | 13 | public static final int Strikethrough = 8; 14 | 15 | public static final int BackgroudColor = 16; 16 | 17 | public static final int SuperScript = 32; 18 | 19 | public static final int SubScript = 64; 20 | 21 | public static final int CheckBox = 1; 22 | 23 | public static final int UnCheckBox = 2; 24 | 25 | public static final int NumList = 3; 26 | 27 | public static final int BulletList = 4; 28 | 29 | public static final int DividingLine = 5; 30 | 31 | public static final int Image = 6; 32 | 33 | public static boolean isWordStyle(int style, int flag) { 34 | return (style & flag) == flag; 35 | } 36 | 37 | public static int setWordStyle(int style, boolean b, int flag) { 38 | return b ? style | flag : (style | flag) ^ flag; 39 | } 40 | 41 | public static int setLineStyle(int style, boolean b, int flag) { 42 | style = b ? flag : (style == flag ? 0 : style); 43 | return style; 44 | } 45 | 46 | public static boolean isLineStyle(int style, int flag) { 47 | return style == flag; 48 | } 49 | 50 | public static int clearLineStyle(int style) { 51 | return 0; 52 | } 53 | 54 | public static int clearWordStyle(int style) { 55 | return 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/mvp/IModel.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.mvp; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/20 5 | */ 6 | public interface IModel { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/mvp/IPresenter.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.mvp; 2 | 3 | import android.arch.lifecycle.Lifecycle; 4 | import android.arch.lifecycle.LifecycleObserver; 5 | import android.arch.lifecycle.LifecycleOwner; 6 | import android.arch.lifecycle.OnLifecycleEvent; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | /** 11 | * Created By Chengjunsen on 2019/2/20 12 | */ 13 | public abstract class IPresenter implements LifecycleObserver { 14 | 15 | @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) 16 | protected abstract void onCreate(@NotNull LifecycleOwner owner); 17 | 18 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 19 | protected abstract void onDestroy(@NotNull LifecycleOwner owner); 20 | 21 | @OnLifecycleEvent(Lifecycle.Event.ON_ANY) 22 | protected void onLifecycleChanged(@NotNull LifecycleOwner owner, @NotNull Lifecycle.Event event) { 23 | switch (event) { 24 | case ON_RESUME: 25 | onResume(); 26 | break; 27 | case ON_START: 28 | onStart(); 29 | break; 30 | case ON_STOP: 31 | onStop(); 32 | break; 33 | case ON_PAUSE: 34 | onPause(); 35 | break; 36 | default: 37 | break; 38 | } 39 | } 40 | 41 | protected void onResume() { 42 | 43 | } 44 | 45 | protected void onStart() { 46 | 47 | } 48 | 49 | protected void onPause() { 50 | 51 | } 52 | 53 | protected void onStop() { 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/mvp/IView.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.mvp; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/20 5 | */ 6 | public interface IView { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/note_edit/EditNoteFormatDialog.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.note_edit; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.jscheng.srich.R; 10 | 11 | public class EditNoteFormatDialog extends Dialog implements View.OnClickListener{ 12 | 13 | private EditNotePresenter mPresenter; 14 | 15 | public EditNoteFormatDialog(Context context, EditNotePresenter presenter) { 16 | super(context, R.style.EditNoteBottomDialogTheme); 17 | setContentView(R.layout.edit_note_bottom_dialog); 18 | this.mPresenter = presenter; 19 | 20 | getWindow().setGravity(Gravity.BOTTOM); 21 | getWindow().setWindowAnimations(R.style.EditNoteBottomDialogAnimTheme); 22 | getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT); 23 | findViewById(R.id.dialog_album).setOnClickListener(this); 24 | findViewById(R.id.dialog_random).setOnClickListener(this); 25 | findViewById(R.id.dialog_network).setOnClickListener(this); 26 | findViewById(R.id.dialog_cancel).setOnClickListener(this); 27 | } 28 | 29 | @Override 30 | public void onClick(View v) { 31 | switch (v.getId()) { 32 | case R.id.dialog_album: 33 | mPresenter.tapAlbum(); 34 | this.dismiss(); 35 | break; 36 | case R.id.dialog_network: 37 | mPresenter.tapNetworkUrl(); 38 | this.dismiss(); 39 | break; 40 | case R.id.dialog_random: 41 | mPresenter.tapRandomUrl(); 42 | this.dismiss(); 43 | case R.id.dialog_cancel: 44 | this.dismiss(); 45 | break; 46 | default: 47 | break; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/note_edit/EditNoteInputDialog.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.note_edit; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.support.v7.widget.AppCompatEditText; 7 | import android.support.v7.widget.AppCompatTextView; 8 | import android.text.TextUtils; 9 | import android.util.DisplayMetrics; 10 | import android.view.Gravity; 11 | import android.view.View; 12 | import android.view.Window; 13 | import android.view.WindowManager; 14 | import android.view.inputmethod.InputMethodManager; 15 | import android.widget.Toast; 16 | 17 | import com.jscheng.srich.R; 18 | 19 | /** 20 | * Created By Chengjunsen on 2019/3/19 21 | */ 22 | public class EditNoteInputDialog implements View.OnClickListener{ 23 | private Dialog dialog; 24 | private Context context; 25 | private AppCompatTextView titleTextView; 26 | private AppCompatEditText contentEditText; 27 | private EditNotePresenter presenter; 28 | 29 | public EditNoteInputDialog(Context context, EditNotePresenter presenter) { 30 | this.context = context; 31 | this.presenter = presenter; 32 | init(); 33 | } 34 | 35 | public String getTitle() { 36 | return titleTextView.getText().toString(); 37 | 38 | } 39 | 40 | public void setTitle(String title) { 41 | titleTextView.setText(title); 42 | } 43 | 44 | public String getContent() { 45 | return contentEditText.getText().toString(); 46 | } 47 | 48 | public void setContent(String content) { 49 | contentEditText.setText(content); 50 | } 51 | 52 | public void dismiss() { 53 | dialog.dismiss(); 54 | } 55 | 56 | private void init() { 57 | this.dialog = new Dialog(context); 58 | dialog.setContentView(R.layout.edit_note_input_dialog); 59 | dialog.getWindow().getDecorView().setPadding(0, 0, 0, 0); 60 | Window window = dialog.getWindow(); 61 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 62 | WindowManager.LayoutParams layoutParams = window.getAttributes(); 63 | dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); 64 | window.setGravity(Gravity.CENTER | Gravity.BOTTOM); 65 | layoutParams.width = (int) (displayMetrics.widthPixels * 1); 66 | window.setAttributes(layoutParams); 67 | titleTextView = dialog.findViewById(R.id.titleTextView); 68 | titleTextView.setOnClickListener(this); 69 | contentEditText = dialog.findViewById(R.id.contentEditText); 70 | dialog.findViewById(R.id.cancelTextView).setOnClickListener(this); 71 | dialog.findViewById(R.id.confirmTextView).setOnClickListener(this); 72 | titleTextView.setText("输入"); 73 | contentEditText.post(new Runnable() { 74 | @Override 75 | public void run() { 76 | InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 77 | inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); 78 | } 79 | }); 80 | } 81 | 82 | public void show() { 83 | dialog.show(); 84 | } 85 | 86 | @Override 87 | public void onClick(View v) { 88 | switch (v.getId()) { 89 | case R.id.confirmTextView: 90 | confirm(); 91 | break; 92 | case R.id.cancelTextView: 93 | dismiss(); 94 | break; 95 | default: 96 | break; 97 | } 98 | } 99 | 100 | private void confirm() { 101 | String url = getContent(); 102 | if (TextUtils.isEmpty(url)) { 103 | Toast.makeText(context, "输入不能为空", Toast.LENGTH_SHORT).show(); 104 | return; 105 | } 106 | 107 | if (!url.startsWith("http")){ 108 | Toast.makeText(context, "链接不合法", Toast.LENGTH_SHORT).show(); 109 | return; 110 | } 111 | 112 | presenter.tapInsertUrl(url); 113 | dismiss(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/note_edit/EditNoteMode.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.note_edit; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/3/11 5 | */ 6 | 7 | public enum EditNoteMode { 8 | /** 9 | * 写模式 10 | */ 11 | Writing, 12 | 13 | /** 14 | * 读模式 15 | */ 16 | Reading, 17 | 18 | /** 19 | * 加载 20 | */ 21 | Loading 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/note_edit/FloatEditButton.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.note_edit; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.view.animation.AlphaAnimation; 8 | import android.view.animation.Animation; 9 | import android.view.animation.AnimationSet; 10 | import android.view.animation.TranslateAnimation; 11 | 12 | import com.jscheng.srich.R; 13 | 14 | /** 15 | * Created By Chengjunsen on 2019/2/22 16 | */ 17 | public class FloatEditButton extends android.support.v7.widget.AppCompatImageView implements View.OnClickListener{ 18 | private static final String NameSpace = "http://schemas.android.com/apk/res/android"; 19 | private EditNotePresenter mPresenter; 20 | 21 | public FloatEditButton(Context context) { 22 | this(context, null); 23 | } 24 | 25 | public FloatEditButton(Context context, AttributeSet attrs) { 26 | this(context, attrs, 0); 27 | } 28 | 29 | public FloatEditButton(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | int srcResource = attrs.getAttributeResourceValue(NameSpace, "src", 0); 32 | init(context, srcResource); 33 | } 34 | 35 | private void init(Context context, int src) { 36 | this.setClickable(true); 37 | super.setOnClickListener(this); 38 | if (src == 0) { 39 | Drawable defaultDrawable = context.getResources().getDrawable(R.mipmap.ic_note_edit_edit, null); 40 | this.setScaleType(ScaleType.CENTER_CROP); 41 | this.setImageDrawable(defaultDrawable); 42 | } 43 | } 44 | 45 | public void setPresenter(EditNotePresenter mPresenter) { 46 | this.mPresenter = mPresenter; 47 | } 48 | 49 | @Override 50 | public void onClick(final View v) { 51 | checkPresenter(); 52 | mPresenter.tapEdit(); 53 | } 54 | 55 | public void show() { 56 | setVisibility(VISIBLE); 57 | } 58 | 59 | public void hide() { 60 | setVisibility(GONE); 61 | } 62 | 63 | private AnimationSet getHideAniamtion() { 64 | AnimationSet animationSet = new AnimationSet(true); 65 | animationSet.addAnimation(new TranslateAnimation(0, 0, 0, getMeasuredHeight())); 66 | animationSet.addAnimation(new AlphaAnimation(getAlpha(), getAlpha()/2)); 67 | animationSet.setDuration(500); 68 | return animationSet; 69 | } 70 | 71 | private AnimationSet getShowAniamtion() { 72 | AnimationSet animationSet = new AnimationSet(true); 73 | animationSet.addAnimation(new TranslateAnimation(0, 0, getMeasuredHeight(), 0)); 74 | animationSet.addAnimation(new AlphaAnimation(getAlpha()/2, getAlpha())); 75 | animationSet.setDuration(300); 76 | return animationSet; 77 | } 78 | 79 | private void checkPresenter() { 80 | if (mPresenter == null) { 81 | throw new RuntimeException("you need to call setPresenter() at first"); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/outline/FloatNewButton.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.outline; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.support.annotation.Nullable; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.animation.Animation; 9 | import android.view.animation.RotateAnimation; 10 | 11 | import com.jscheng.srich.R; 12 | 13 | /** 14 | * Created By Chengjunsen on 2019/2/21 15 | */ 16 | public class FloatNewButton extends android.support.v7.widget.AppCompatImageView implements View.OnClickListener{ 17 | 18 | private static final String NameSpace = "http://schemas.android.com/apk/res/android"; 19 | 20 | private OnClickListener mClickListener = null; 21 | 22 | private Animation mAnimation = null; 23 | 24 | public FloatNewButton(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public FloatNewButton(Context context, AttributeSet attrs) { 29 | this(context, attrs, 0); 30 | } 31 | 32 | public FloatNewButton(Context context, AttributeSet attrs, int defStyleAttr) { 33 | super(context, attrs, defStyleAttr); 34 | int srcResource = attrs.getAttributeResourceValue(NameSpace, "src", 0); 35 | init(context, srcResource); 36 | } 37 | 38 | private void init(Context context, int src) { 39 | this.setClickable(true); 40 | super.setOnClickListener(this); 41 | if (src == 0) { 42 | Drawable defaultDrawable =context.getResources().getDrawable(R.mipmap.ic_compose, null); 43 | this.setScaleType(ScaleType.CENTER_CROP); 44 | this.setImageDrawable(defaultDrawable); 45 | } 46 | 47 | this.mAnimation = new RotateAnimation(0, 90, 48 | Animation.RELATIVE_TO_SELF,0.5f, 49 | Animation.RELATIVE_TO_SELF,0.5f); 50 | 51 | this.mAnimation.setDuration(500); 52 | } 53 | 54 | @Override 55 | public void setOnClickListener(@Nullable OnClickListener listener) { 56 | this.mClickListener = listener; 57 | } 58 | 59 | @Override 60 | public void onClick(final View v) { 61 | this.startAnimation(mAnimation); 62 | mAnimation.setAnimationListener(new Animation.AnimationListener() { 63 | @Override 64 | public void onAnimationStart(Animation animation) { } 65 | 66 | @Override 67 | public void onAnimationEnd(Animation animation) { 68 | if (mClickListener != null) { 69 | mClickListener.onClick(v); 70 | } 71 | } 72 | 73 | @Override 74 | public void onAnimationRepeat(Animation animation) { } 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/outline/OutLineCenterDialog.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.outline; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.support.annotation.NonNull; 6 | import android.view.Gravity; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.jscheng.srich.R; 11 | 12 | /** 13 | * Created By Chengjunsen on 2019/3/19 14 | */ 15 | public class OutLineCenterDialog extends Dialog implements View.OnClickListener{ 16 | 17 | private OutLinePresenter mPresenter; 18 | private String noteid; 19 | 20 | public OutLineCenterDialog(@NonNull Context context, OutLinePresenter mPresenter) { 21 | super(context); 22 | setContentView(R.layout.outline_center_dialog); 23 | this.mPresenter = mPresenter; 24 | 25 | getWindow().setGravity(Gravity.CENTER); 26 | getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT); 27 | findViewById(R.id.delete_view).setOnClickListener(this); 28 | } 29 | 30 | public void show(String noteid) { 31 | this.noteid = noteid; 32 | super.show(); 33 | } 34 | 35 | @Override 36 | public void onClick(View v) { 37 | switch (v.getId()) { 38 | case R.id.delete_view: 39 | mPresenter.tapDelete(noteid); 40 | super.dismiss(); 41 | break; 42 | default: 43 | break; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/outline/OutLineModel.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.outline; 2 | 3 | import com.jscheng.srich.model.Note; 4 | import com.jscheng.srich.model.OutLine; 5 | import com.jscheng.srich.utils.DateUtil; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.List; 11 | 12 | /** 13 | * Created By Chengjunsen on 2019/2/21 14 | */ 15 | public class OutLineModel { 16 | 17 | public static List build(List notes) { 18 | List outLines = new ArrayList<>(); 19 | Collections.sort(notes, new Comparator() { 20 | @Override 21 | public int compare(Note note1, Note note2) { 22 | return (int)(note2.getModifyTime() - note1.getModifyTime()); 23 | } 24 | }); 25 | 26 | OutLine newDateOutLine = null; 27 | for (int i = 0; i < notes.size(); i++) { 28 | Note note = notes.get(i); 29 | if (newDateOutLine == null || !DateUtil.isSameDate(newDateOutLine.getTime(), note.getModifyTime())) { 30 | newDateOutLine = buildDateOutLine(note); 31 | outLines.add(newDateOutLine); 32 | } 33 | outLines.add(new OutLine(note)); 34 | } 35 | return outLines; 36 | } 37 | 38 | private static OutLine buildDateOutLine(Note note) { 39 | return new OutLine(note.getModifyTime()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/outline/OutLinePresenter.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.outline; 2 | 3 | import android.arch.lifecycle.LifecycleOwner; 4 | import android.content.Context; 5 | 6 | import com.jscheng.srich.model.NoteModel; 7 | import com.jscheng.srich.model.Note; 8 | import com.jscheng.srich.mvp.IPresenter; 9 | import com.jscheng.srich.mvp.IView; 10 | import com.jscheng.srich.route.Router; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | import java.util.List; 14 | 15 | import io.reactivex.Observable; 16 | import io.reactivex.ObservableEmitter; 17 | import io.reactivex.ObservableOnSubscribe; 18 | import io.reactivex.android.schedulers.AndroidSchedulers; 19 | import io.reactivex.functions.Consumer; 20 | import io.reactivex.schedulers.Schedulers; 21 | 22 | /** 23 | * Created By Chengjunsen on 2019/2/20 24 | */ 25 | public class OutLinePresenter extends IPresenter { 26 | private OutLineView mView; 27 | 28 | public interface OutLineView extends IView { 29 | void setData(List ntoes); 30 | void showCenterDialog(String id); 31 | } 32 | 33 | @Override 34 | public void onCreate(@NotNull LifecycleOwner owner) { 35 | this.mView = (OutLineView) owner; 36 | } 37 | 38 | @Override 39 | public void onDestroy(@NotNull LifecycleOwner owner) { 40 | this.mView = null; 41 | } 42 | 43 | @Override 44 | protected void onResume() { 45 | this.reload(); 46 | } 47 | 48 | public void reload() { 49 | Observable.create(new ObservableOnSubscribe>() { 50 | @Override 51 | public void subscribe(ObservableEmitter> emitter) throws Exception { 52 | List notes = NoteModel.getNotes((Context)mView); 53 | emitter.onNext(notes); 54 | } 55 | }).subscribeOn(Schedulers.io()) 56 | .observeOn(AndroidSchedulers.mainThread()) 57 | .subscribe(new Consumer>() { 58 | @Override 59 | public void accept(List notes) throws Exception { 60 | mView.setData(notes); 61 | } 62 | }); 63 | } 64 | 65 | public void tapNew() { 66 | Router.with((Context)mView).route("editnote").go(); 67 | } 68 | 69 | public void tapNote(String id) { 70 | Router.with((Context)mView).route("editnote").intent("id", id).go(); 71 | } 72 | 73 | public void taplongNote(String id) { 74 | mView.showCenterDialog(id); 75 | } 76 | 77 | public void tapDelete(String id) { 78 | NoteModel.deleteNote((Context)mView, id); 79 | reload(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/outline/OutLinesActivity.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.outline; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.View; 10 | import android.widget.RelativeLayout; 11 | import android.widget.TextView; 12 | 13 | import com.jscheng.annotations.Route; 14 | import com.jscheng.srich.BaseActivity; 15 | import com.jscheng.srich.R; 16 | import com.jscheng.srich.model.Note; 17 | import com.jscheng.srich.utils.DateUtil; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * 预览页 23 | * Created By Chengjunsen on 2019/2/20 24 | */ 25 | @Route("outline") 26 | public class OutLinesActivity extends BaseActivity 27 | implements OutLinePresenter.OutLineView, View.OnClickListener, SwipeRefreshLayout.OnRefreshListener{ 28 | private final static String TAG = "OutLinesActivity"; 29 | private OutLinePresenter mPresenter; 30 | private RecyclerView mRecyclerView; 31 | private LinearLayoutManager mLayoutManager; 32 | private OutLinesAdapter mRecyclerAdapter ; 33 | private RelativeLayout mHeadDateLayout; 34 | private TextView mHeadDateText; 35 | private FloatNewButton mFloatButton; 36 | private SwipeRefreshLayout mSwipeLayout; 37 | private OutLineCenterDialog mCenterDialog; 38 | 39 | @Override 40 | protected void onCreate(@Nullable Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_outline); 43 | 44 | this.mPresenter = new OutLinePresenter(); 45 | this.getLifecycle().addObserver(mPresenter); 46 | 47 | this.mHeadDateLayout = findViewById(R.id.outline_head_date); 48 | this.mHeadDateText = mHeadDateLayout.findViewById(R.id.date_text); 49 | this.mFloatButton = findViewById(R.id.float_new_button); 50 | this.mFloatButton.setOnClickListener(this); 51 | this.mSwipeLayout = findViewById(R.id.swipe_layout); 52 | this.mSwipeLayout.setOnRefreshListener(this); 53 | 54 | this.mRecyclerView = findViewById(R.id.outline_recyclerview); 55 | this.mLayoutManager = new LinearLayoutManager(this, 56 | LinearLayoutManager.VERTICAL, false); 57 | this.mRecyclerAdapter = new OutLinesAdapter(mPresenter, mRecyclerView, mLayoutManager); 58 | this.mRecyclerView.setLayoutManager(mLayoutManager); 59 | this.mRecyclerView.setAdapter(mRecyclerAdapter); 60 | this.mRecyclerView.addOnScrollListener(new ScrollChangeListener()); 61 | } 62 | 63 | @Override 64 | public void setData(List notes) { 65 | mSwipeLayout.setRefreshing(false); 66 | mRecyclerAdapter.setData(notes); 67 | } 68 | 69 | @Override 70 | public void showCenterDialog(String id) { 71 | if (mCenterDialog == null) { 72 | mCenterDialog = new OutLineCenterDialog(this, mPresenter); 73 | } 74 | mCenterDialog.show(id); 75 | } 76 | 77 | private void tapNewButton() { 78 | mPresenter.tapNew(); 79 | } 80 | 81 | @Override 82 | public void onClick(View v) { 83 | switch (v.getId()) { 84 | case R.id.float_new_button: 85 | tapNewButton(); 86 | break; 87 | default: 88 | break; 89 | } 90 | } 91 | 92 | @Override 93 | public void onRefresh() { 94 | mPresenter.reload(); 95 | } 96 | 97 | private class ScrollChangeListener extends RecyclerView.OnScrollListener { 98 | @Override 99 | public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 100 | super.onScrolled(recyclerView, dx, dy); 101 | updateHeadDateLayout(); 102 | } 103 | } 104 | 105 | private void updateHeadDateLayout() { 106 | long time = mRecyclerAdapter.getFirstVisibleDateTime(); 107 | if (time > 0) { 108 | mHeadDateLayout.setVisibility(View.VISIBLE); 109 | mHeadDateText.setText(DateUtil.formatDate(time)); 110 | } else { 111 | mHeadDateLayout.setVisibility(View.INVISIBLE); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/revoke/NoteRevocationManager.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.revoke; 2 | 3 | import com.jscheng.srich.model.Note; 4 | import com.jscheng.srich.model.NoteSnap; 5 | import com.jscheng.srich.model.NoteSnapBuilder; 6 | 7 | import java.util.LinkedList; 8 | 9 | /** 10 | * Created By Chengjunsen on 2019/3/18 11 | * 撤销与反撤销 12 | */ 13 | public class NoteRevocationManager { 14 | 15 | /** 16 | * 最大撤销步数 17 | */ 18 | private final static int MAX_REVOCATION_STEP_COUNT = 10; 19 | 20 | /** 21 | * 最大间隔时间,可当做是同一动作 22 | */ 23 | private final static int MAX_INTERVER_MILLTIME = 1000; 24 | 25 | /** 26 | * 撤销栈 27 | */ 28 | private LinkedList mRevocationList; 29 | 30 | /** 31 | * 恢复栈 32 | */ 33 | private LinkedList mRecoveryList; 34 | 35 | /** 36 | * 可连续的动作 37 | */ 38 | private boolean isContinuousAction; 39 | 40 | private NoteSnap currentSnap; 41 | 42 | private boolean isRunning; 43 | 44 | public NoteRevocationManager() { 45 | this.mRecoveryList = new LinkedList<>(); 46 | this.mRevocationList = new LinkedList<>(); 47 | this.isRunning = false; 48 | this.isContinuousAction = false; 49 | this.currentSnap = null; 50 | } 51 | 52 | /** 53 | * 恢复 54 | */ 55 | public NoteSnap recover(Note note, int selectionBegin, int selectionEnd) { 56 | if (mRecoveryList.isEmpty()){ 57 | return null; 58 | } 59 | 60 | NoteSnap currentSnap = new NoteSnapBuilder(note) 61 | .continuous(isContinuousAction) 62 | .selection(selectionBegin, selectionEnd) 63 | .build(); 64 | mRevocationList.push(currentSnap); 65 | return mRecoveryList.pop(); 66 | } 67 | 68 | /** 69 | * 撤销 70 | */ 71 | public NoteSnap revoke(Note note, int selectionBegin, int selectionEnd) { 72 | if (mRevocationList.isEmpty()) { 73 | return null; 74 | } 75 | NoteSnap currentSnap = new NoteSnapBuilder(note) 76 | .continuous(isContinuousAction) 77 | .selection(selectionBegin, selectionEnd) 78 | .build(); 79 | mRecoveryList.push(currentSnap); 80 | return mRevocationList.pop(); 81 | } 82 | 83 | /** 84 | * 是否可以恢复 85 | */ 86 | public boolean isCanRecover() { 87 | return mRecoveryList.size() > 0; 88 | } 89 | 90 | /** 91 | * 是否可以撤销 92 | */ 93 | public boolean isCanRevoke() { 94 | return mRevocationList.size() > 0; 95 | } 96 | 97 | /** 98 | * 开始动作 99 | */ 100 | public void beginAction(Note note, int selectionBegin, int selectionEnd, boolean isContinuous) { 101 | if (isRunning) { 102 | throw new RuntimeException("you should end action before"); 103 | } 104 | isRunning = true; 105 | isContinuousAction = isContinuous; 106 | 107 | currentSnap = new NoteSnapBuilder(note) 108 | .continuous(isContinuousAction) 109 | .selection(selectionBegin, selectionEnd) 110 | .build(); 111 | } 112 | 113 | /** 114 | * 结束动作 115 | */ 116 | public void endAction() { 117 | if (!isRunning) { 118 | throw new RuntimeException("you should begin action before"); 119 | } 120 | isRunning = false; 121 | mRecoveryList.clear(); 122 | 123 | if (!mRevocationList.isEmpty()) { 124 | NoteSnap lastSnap = mRevocationList.getLast(); 125 | if (lastSnap.isContinuousAction() && isContinuousAction && 126 | Math.abs(currentSnap.getTime() - lastSnap.getTime()) < MAX_INTERVER_MILLTIME) { 127 | return; 128 | } 129 | } 130 | 131 | mRevocationList.push(currentSnap); 132 | checkMaxActions(); 133 | } 134 | 135 | private void checkMaxActions() { 136 | while (mRevocationList.size() > MAX_REVOCATION_STEP_COUNT) { 137 | mRevocationList.removeLast(); 138 | } 139 | } 140 | 141 | public boolean isRunning() { 142 | return isRunning; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/ActivityInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | 8 | /** 9 | * Created By Chengjunsen on 2019/2/21 10 | */ 11 | public class ActivityInterceptor implements RouterInterceptor { 12 | 13 | @Override 14 | public RouterResponse process(RouterChain chain) { 15 | RouterRequest request = chain.getRequest(); 16 | RouterResponse response = chain.proceed(request); 17 | if (response.isDone() || !isAimInstance(response.getCls())) { 18 | return response; 19 | } 20 | return doRequst(request, response); 21 | } 22 | 23 | private boolean isAimInstance(Class cls) { 24 | if (cls == null) { 25 | return false; 26 | } 27 | boolean isActivity = Activity.class.isAssignableFrom(cls); 28 | return isActivity; 29 | } 30 | 31 | private RouterResponse doRequst(RouterRequest request, RouterResponse response) { 32 | Class activityCls = response.getCls(); 33 | Context context = request.getContext(); 34 | Bundle bundle = request.getBundle(); 35 | Intent intent = new Intent(context, activityCls); 36 | 37 | response.setIntent(intent); 38 | response.setDone(true); 39 | 40 | if (request.isJump()) { 41 | intent.putExtras(bundle); 42 | context.startActivity(intent); 43 | } 44 | return response; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/IRouteInfo.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/3/18 7 | */ 8 | public interface IRouteInfo { 9 | void load(Map routes); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/LogInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/21 7 | */ 8 | public class LogInterceptor implements RouterInterceptor { 9 | private static final String TAG= "RouterLog"; 10 | @Override 11 | public RouterResponse process(RouterChain chain) { 12 | RouterRequest request = chain.getRequest(); 13 | Log.d(TAG, request.getUri()); 14 | RouterResponse response = chain.proceed(request); 15 | return response; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/RouteInfoUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.text.TextUtils; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Enumeration; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Set; 17 | import java.util.concurrent.ArrayBlockingQueue; 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.concurrent.ThreadPoolExecutor; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | import dalvik.system.DexFile; 23 | 24 | /** 25 | * Created By Chengjunsen on 2019/3/18 26 | */ 27 | public class RouteInfoUtil { 28 | /** 29 | * 获得程序所有的apk(instant run会产生很多split apk) 30 | * @param context 31 | * @return 32 | * @throws PackageManager.NameNotFoundException 33 | */ 34 | private static List getSourcePaths(Context context) throws PackageManager.NameNotFoundException { 35 | ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0); 36 | List sourcePaths = new ArrayList<>(); 37 | sourcePaths.add(applicationInfo.sourceDir); 38 | //instant run 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 40 | if (null != applicationInfo.splitSourceDirs) { 41 | sourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs)); 42 | } 43 | } 44 | return sourcePaths; 45 | } 46 | 47 | /** 48 | * 得到路由表的类名 49 | * @param context 50 | * @param packageName 51 | * @return 52 | * @throws PackageManager.NameNotFoundException 53 | * @throws InterruptedException 54 | */ 55 | public static Set getFileNameByPackageName(Application context, final String packageName) 56 | throws PackageManager.NameNotFoundException, InterruptedException { 57 | final Set classNames = new HashSet<>(); 58 | List paths = getSourcePaths(context); 59 | //使用同步计数器判断均处理完成 60 | final CountDownLatch countDownLatch = new CountDownLatch(paths.size()); 61 | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 62 | paths.size(), 63 | paths.size(), 64 | 30, 65 | TimeUnit.SECONDS, new 66 | ArrayBlockingQueue(64)); 67 | 68 | for (final String path : paths) { 69 | threadPoolExecutor.execute(new Runnable() { 70 | @Override 71 | public void run() { 72 | DexFile dexFile = null; 73 | try { 74 | //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类 75 | dexFile = new DexFile(path); 76 | Enumeration dexEntries = dexFile.entries(); 77 | while (dexEntries.hasMoreElements()) { 78 | String className = dexEntries.nextElement(); 79 | if (!TextUtils.isEmpty(className) && className.startsWith(packageName)) { 80 | classNames.add(className); 81 | } 82 | } 83 | } catch (IOException e) { 84 | e.printStackTrace(); 85 | } finally { 86 | if (null != dexFile) { 87 | try { 88 | dexFile.close(); 89 | } catch (IOException e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | //释放一个 94 | countDownLatch.countDown(); 95 | } 96 | } 97 | }); 98 | } 99 | //等待执行完成 100 | countDownLatch.await(); 101 | return classNames; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/Router.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | /** 15 | * 基于APT写的简单路由(可去掉) 16 | * Created By Chengjunsen on 2019/2/21 17 | */ 18 | public class Router { 19 | private static final String ROUTE_ROOT_PAKCAGE = "com.jscheng.processor"; 20 | 21 | private static HashMap mRouteTable = new HashMap<>(); 22 | 23 | private static List mInterceptor = new ArrayList<>(); 24 | 25 | public static void addUri(String uri, Class cls) { 26 | mRouteTable.put(uri, cls); 27 | } 28 | 29 | public static void addInterceptor(RouterInterceptor interceptor) { 30 | mInterceptor.add(interceptor); 31 | } 32 | 33 | public static Map getRouteTable() { 34 | return mRouteTable; 35 | } 36 | 37 | public static List getInterceptor() { 38 | return mInterceptor; 39 | } 40 | 41 | public static RouterRequest with(Context context) { 42 | RouterRequest request = new RouterRequest(context); 43 | return request; 44 | } 45 | 46 | public static void init(Application application) { 47 | initInterceptor(); 48 | initRoute(application); 49 | } 50 | 51 | private static void initInterceptor() { 52 | Router.addInterceptor(new LogInterceptor()); 53 | Router.addInterceptor(new ActivityInterceptor()); 54 | Router.addInterceptor(new UriAnalyzerInterceptor()); 55 | } 56 | 57 | private static void initRoute(Application application) { 58 | try { 59 | Set routerMap = RouteInfoUtil.getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE); 60 | for (String className : routerMap) { 61 | ((IRouteInfo) Class.forName(className).getConstructor().newInstance()).load(mRouteTable); 62 | } 63 | } catch (PackageManager.NameNotFoundException | 64 | ClassNotFoundException | 65 | InterruptedException | 66 | InstantiationException | 67 | InvocationTargetException | 68 | NoSuchMethodException | 69 | IllegalAccessException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/RouterChain.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import java.util.List; 4 | import java.util.Stack; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/2/21 8 | */ 9 | public class RouterChain { 10 | 11 | private RouterRequest request; 12 | 13 | private List interceptors; 14 | 15 | private int index; 16 | 17 | public RouterChain(RouterRequest request, List interceptors) { 18 | this.request = request; 19 | this.interceptors = interceptors; 20 | this.index = 0; 21 | } 22 | 23 | public RouterRequest getRequest() { 24 | return request; 25 | } 26 | 27 | public RouterResponse proceed(RouterRequest request) { 28 | this.request = request; 29 | if (index < 0 || index >= interceptors.size()) { 30 | return new RouterResponse(); 31 | } 32 | return interceptors.get(index++).process(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/RouterInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/21 5 | */ 6 | public interface RouterInterceptor { 7 | 8 | RouterResponse process(RouterChain chain); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/RouterRequest.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.TextUtils; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/2/21 13 | */ 14 | public class RouterRequest { 15 | private Context context; 16 | private String uri; 17 | private boolean isJump; 18 | private Bundle bundle; 19 | 20 | public RouterRequest(Context context) { 21 | this.context = context; 22 | this.isJump = false; 23 | this.bundle = new Bundle(); 24 | } 25 | 26 | public RouterRequest route(String uri) { 27 | this.uri = uri; 28 | return this; 29 | } 30 | 31 | public RouterRequest intent(Intent intent) { 32 | this.bundle = intent.getExtras(); 33 | return this; 34 | } 35 | 36 | public RouterRequest intent(String key, int what) { 37 | if (bundle == null) { 38 | bundle = new Bundle(); 39 | } 40 | this.bundle.putInt(key, what); 41 | return this; 42 | } 43 | 44 | public RouterRequest intent(String key, String what) { 45 | if (bundle == null) { 46 | bundle = new Bundle(); 47 | } 48 | this.bundle.putString(key, what); 49 | return this; 50 | } 51 | 52 | public RouterRequest intent(String key, List what) { 53 | if (bundle == null) { 54 | bundle = new Bundle(); 55 | } 56 | this.bundle.putStringArrayList(key, new ArrayList(what)); 57 | return this; 58 | } 59 | 60 | 61 | public RouterRequest intent(Bundle bundle) { 62 | if (bundle == null) { 63 | bundle = new Bundle(); 64 | } 65 | this.bundle.putAll(bundle); 66 | return this; 67 | } 68 | 69 | public boolean go() { 70 | check(); 71 | this.isJump = true; 72 | return process().isDone(); 73 | } 74 | 75 | public Bundle getBundle() { 76 | check(); 77 | if (bundle == null) { 78 | bundle = new Bundle(); 79 | } 80 | return bundle; 81 | } 82 | 83 | private RouterResponse process() { 84 | List interceptors = Router.getInterceptor(); 85 | RouterChain chain = new RouterChain(this, interceptors); 86 | return chain.proceed(this); 87 | } 88 | 89 | private void check() { 90 | if (context == null) { 91 | throw new RuntimeException("context cannot be null"); 92 | } 93 | if (TextUtils.isEmpty(uri)) { 94 | throw new RuntimeException("uri cannot be null or isEmpty"); 95 | } 96 | Object object = Router.getRouteTable().get(uri); 97 | if ( object == null) { 98 | throw new RuntimeException("you should register uri at first"); 99 | } 100 | } 101 | 102 | public String getUri() { 103 | return uri; 104 | } 105 | 106 | public Context getContext() { 107 | return context; 108 | } 109 | 110 | public boolean isJump() { 111 | return isJump; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/RouterResponse.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | import android.content.Intent; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/21 7 | */ 8 | public class RouterResponse { 9 | 10 | private boolean isDone; 11 | 12 | private String msg; 13 | 14 | private Intent intent; 15 | 16 | private Class cls; 17 | 18 | public boolean isDone() { 19 | return isDone; 20 | } 21 | 22 | public void setDone(boolean done) { 23 | isDone = done; 24 | } 25 | 26 | public String getMsg() { 27 | return msg; 28 | } 29 | 30 | public void setMsg(String msg) { 31 | this.msg = msg; 32 | } 33 | 34 | public Intent getIntent() { 35 | return intent; 36 | } 37 | 38 | public void setIntent(Intent intent) { 39 | this.intent = intent; 40 | } 41 | 42 | public void setCls(Class cls) { 43 | this.cls = cls; 44 | } 45 | 46 | public Class getCls() { 47 | return cls; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/route/UriAnalyzerInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.route; 2 | 3 | /** 4 | * Created By Chengjunsen on 2019/2/25 5 | */ 6 | public class UriAnalyzerInterceptor implements RouterInterceptor { 7 | @Override 8 | public RouterResponse process(RouterChain chain) { 9 | RouterRequest request = chain.getRequest(); 10 | RouterResponse response = chain.proceed(request); 11 | String uri = request.getUri(); 12 | Class cls = Router.getRouteTable().get(uri); 13 | response.setCls(cls); 14 | return response; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/ClipboardUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | 7 | public class ClipboardUtil { 8 | 9 | public static void copy(String content, Context context) 10 | { 11 | ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 12 | // 创建一个剪贴数据集,包含一个普通文本数据条目(需要复制的数据) 13 | ClipData clipData = ClipData.newPlainText(null, content); 14 | // 把数据集设置(复制)到剪贴板 15 | clipboard.setPrimaryClip(clipData); 16 | } 17 | 18 | public static String paste(Context context) 19 | { 20 | // 获取系统剪贴板 21 | ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 22 | // 获取剪贴板的剪贴数据集 23 | ClipData clipData = clipboard.getPrimaryClip(); 24 | if (clipData != null && clipData.getItemCount() > 0) { 25 | // 从数据集中获取(粘贴)第一条文本数据 26 | CharSequence text = clipData.getItemAt(0).getText(); 27 | return text.toString(); 28 | } 29 | return null; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.Date; 6 | 7 | /** 8 | * Created By Chengjunsen on 2019/2/21 9 | */ 10 | public class DateUtil { 11 | public static boolean isSameDate(long time1, long time2) { 12 | Calendar cal1 = Calendar.getInstance(); 13 | cal1.setTimeInMillis(time1); 14 | Calendar cal2 = Calendar.getInstance(); 15 | cal2.setTimeInMillis(time2); 16 | return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && 17 | cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && 18 | cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH); 19 | } 20 | 21 | public static String formatDate(long time) { 22 | SimpleDateFormat fmt = new SimpleDateFormat("yyyy年MM月dd日"); 23 | return fmt.format(new Date(time)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/DisplayUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.content.Context; 4 | import android.view.WindowManager; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/2/25 8 | */ 9 | public class DisplayUtil { 10 | public static int getScreenHeight(Context context) { 11 | WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 12 | return manager.getDefaultDisplay().getHeight(); 13 | } 14 | 15 | public static int getScreenWidth(Context context) { 16 | WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 17 | return manager.getDefaultDisplay().getWidth(); 18 | } 19 | 20 | /** 21 | * 将px转换为与之相等的dp 22 | */ 23 | public static int px2dp(Context context, float pxValue) { 24 | final float scale = context.getResources().getDisplayMetrics().density; 25 | return (int) (pxValue / scale + 0.5f); 26 | } 27 | 28 | /** 29 | * 将dp转换为与之相等的px 30 | */ 31 | public static int dp2px(Context context, float dipValue) { 32 | final float scale = context.getResources().getDisplayMetrics().density; 33 | return (int) (dipValue * scale + 0.5f); 34 | } 35 | 36 | /** 37 | * 将px转换为sp 38 | */ 39 | public static int px2sp(Context context, float pxValue) { 40 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 41 | return (int) (pxValue / fontScale + 0.5f); 42 | } 43 | 44 | /** 45 | * 将sp转换为px 46 | */ 47 | public static int sp2px(Context context, float spValue) { 48 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 49 | return (int) (spValue * fontScale + 0.5f); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/FontUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.text.TextUtils; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | import java.lang.ref.SoftReference; 14 | import java.util.Hashtable; 15 | 16 | /** 17 | * Created By Chengjunsen on 2019/2/22 18 | */ 19 | public class FontUtil { 20 | 21 | public static void setCustomFont(View view, Context ctx, AttributeSet attrs, 22 | int[] attributeSet, int fontId) { 23 | TypedArray a = ctx.obtainStyledAttributes(attrs, attributeSet); 24 | String customFont = a.getString(fontId); 25 | setCustomFont(view, ctx, customFont); 26 | a.recycle(); 27 | } 28 | 29 | public static boolean setCustomFont(View view, Context ctx, String asset) { 30 | if (TextUtils.isEmpty(asset)) { 31 | return false; 32 | } 33 | Typeface tf = null; 34 | try { 35 | tf = getFont(ctx, asset); 36 | if (view instanceof TextView) { 37 | ((TextView) view).setTypeface(tf); 38 | } else { 39 | ((Button) view).setTypeface(tf); 40 | } 41 | } catch (Exception e) { 42 | Log.e("AppUtil", "Could not get typface " + asset); 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | private static final Hashtable> fontCache = new Hashtable>(); 50 | 51 | public static Typeface getFont(Context c, String name) { 52 | synchronized (fontCache) { 53 | if (fontCache.get(name) != null) { 54 | SoftReference ref = fontCache.get(name); 55 | if (ref.get() != null) { 56 | return ref.get(); 57 | } 58 | } 59 | 60 | Typeface typeface = Typeface.createFromAsset(c.getAssets(), name); 61 | fontCache.put(name, new SoftReference(typeface)); 62 | 63 | return typeface; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/KeyboardUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.content.Context; 4 | import android.text.InputType; 5 | import android.view.View; 6 | import android.view.inputmethod.InputMethodManager; 7 | import android.widget.EditText; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/2/26 13 | */ 14 | public class KeyboardUtil { 15 | /** 16 | * 显示软键盘 17 | * @param context 18 | * @param view 19 | */ 20 | public static void showSoftInput(final Context context, final View view) { 21 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 22 | imm.showSoftInput(view, 0); 23 | } 24 | 25 | /** 26 | * 隐藏软键盘 27 | * @param context 28 | * @param view 29 | */ 30 | public static void hideSoftInput(Context context, View view) { 31 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 32 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 33 | } 34 | 35 | /** 36 | * 获取软键盘状态 37 | * @param context 38 | * @return 39 | */ 40 | public static boolean isShowSoftInput(Context context, View view) { 41 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 42 | //获取状态信息 43 | return imm.isActive(view);//true 打开 44 | } 45 | 46 | public static void configSoftInput(EditText editText, boolean isShow){ 47 | Class cls = EditText.class; 48 | Method method; 49 | try { 50 | method = cls.getMethod("setShowSoftInputOnFocus", boolean.class); 51 | method.setAccessible(true); 52 | method.invoke(editText, isShow); 53 | } catch (Exception e) { 54 | } 55 | 56 | try { 57 | method = cls.getMethod("setSoftInputShownOnFocus", boolean.class); 58 | method.setAccessible(true); 59 | method.invoke(editText, isShow); 60 | } catch (Exception e) { 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/MdUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import java.security.MessageDigest; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/3/9 7 | */ 8 | public class MdUtil { 9 | public final static String encode(String s) { 10 | char hexDigits[] = { '0', '1', '2', '3', '4', 11 | '5', '6', '7', '8', '9', 12 | 'a', 'b', 'c', 'd', 'e', 'f' }; 13 | try { 14 | byte[] btInput = s.getBytes(); 15 | //获得MD5摘要算法的 MessageDigest 对象 16 | MessageDigest mdInst = MessageDigest.getInstance("MD5"); 17 | //使用指定的字节更新摘要 18 | mdInst.update(btInput); 19 | //获得密文 20 | byte[] md = mdInst.digest(); 21 | //把密文转换成十六进制的字符串形式 22 | int j = md.length; 23 | char str[] = new char[j * 2]; 24 | int k = 0; 25 | for (int i = 0; i < j; i++) { 26 | byte byte0 = md[i]; 27 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 28 | str[k++] = hexDigits[byte0 & 0xf]; 29 | } 30 | return new String(str); 31 | } 32 | catch (Exception e) { 33 | e.printStackTrace(); 34 | return null; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/OsUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.os.Build; 4 | 5 | /** 6 | * Created By Chengjunsen on 2019/2/22 7 | */ 8 | public final class OsUtil { 9 | public static boolean isFlyme() { 10 | String manufacturer = Build.MANUFACTURER; 11 | if ("meizu".equalsIgnoreCase(manufacturer)) { 12 | return true; 13 | } 14 | return false; 15 | } 16 | 17 | public static boolean isEMUI() { 18 | String manufacturer = Build.MANUFACTURER; 19 | if ("huawei".equalsIgnoreCase(manufacturer)) { 20 | return true; 21 | } 22 | return false; 23 | } 24 | 25 | public static boolean isMIUI() { 26 | String manufacturer = Build.MANUFACTURER; 27 | if ("xiaomi".equalsIgnoreCase(manufacturer)) { 28 | return true; 29 | } 30 | return false; 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/StorageUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.BufferedWriter; 10 | import java.io.FileReader; 11 | import java.io.FileWriter; 12 | import java.io.IOException; 13 | 14 | /** 15 | * Created By Chengjunsen on 2019/3/8 16 | */ 17 | public class StorageUtil { 18 | private static final String TAG = "StorageUtil"; 19 | 20 | public static String readFile(String path) { 21 | BufferedReader reader = null; 22 | StringBuilder content = new StringBuilder(); 23 | try { 24 | String line; 25 | reader = new BufferedReader(new FileReader(path)); 26 | while ((line = reader.readLine()) != null) { 27 | content.append(line); 28 | } 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | return content.toString(); 33 | } 34 | 35 | public static void overwiteFile(String path, String text) { 36 | if (TextUtils.isEmpty(text)) { 37 | Log.e(TAG, "overwiteFile: path is null"); 38 | return; 39 | } 40 | BufferedWriter writer = null; 41 | try { 42 | writer = new BufferedWriter(new FileWriter(path)); 43 | writer.write(text); 44 | writer.flush(); 45 | writer.close(); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | 51 | public static String getDiskCachePath(Context context){ 52 | String cachePath; 53 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) 54 | || !Environment.isExternalStorageRemovable()) { 55 | cachePath = context.getExternalCacheDir().getPath(); 56 | } else { 57 | cachePath = context.getCacheDir().getPath(); 58 | } 59 | return cachePath; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/jscheng/srich/utils/VersionUtil.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | 6 | /** 7 | * Created By Chengjunsen on 2019/3/9 8 | */ 9 | public class VersionUtil { 10 | /** 11 | * 获取本地apk的版本 12 | * @param mContext 13 | * @return 14 | */ 15 | public static int getVersionCode(Context mContext) { 16 | int versionCode = 0; 17 | try { 18 | //获取软件版本号,对应AndroidManifest.xml下android:versionCode 19 | versionCode = mContext.getPackageManager(). 20 | getPackageInfo(mContext.getPackageName(), 0).versionCode; 21 | } catch (PackageManager.NameNotFoundException e) { 22 | e.printStackTrace(); 23 | } 24 | return versionCode; 25 | } 26 | 27 | /** 28 | * 获取版本号名称 29 | * 30 | * @param context 上下文 31 | * @return 32 | */ 33 | public static String getVerName(Context context) { 34 | String verName = ""; 35 | try { 36 | verName = context.getPackageManager(). 37 | getPackageInfo(context.getPackageName(), 0).versionName; 38 | } catch (PackageManager.NameNotFoundException e) { 39 | e.printStackTrace(); 40 | } 41 | return verName; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/anim/edit_note_bottom_in_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/edit_note_bottom_out_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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/editor_bar_format_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_bar_redo_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_bar_undo_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_note_bottom_dialog_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_note_dialog_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_note_dialog_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_note_dialog_text_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_note_dialog_text_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/editor_view_select.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/outline_toolbar_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_editnote.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 17 | 25 | 31 | 32 | 33 | 40 | 41 | 42 | 50 | 51 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_image_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_outline.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | 23 | 26 | 27 | 36 | 37 | 38 | 47 | 48 | 49 | 50 | 51 | 52 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/layout/edit_note_bottom_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 25 | 26 | 34 | 35 | 39 | 40 | 48 | 49 | 53 | 54 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/edit_note_input_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 29 | 30 | 34 | 35 | 39 | 40 | 51 | 52 | 56 | 57 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/edit_note_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 31 | 32 | 44 | 45 | 57 | 58 | 69 | 70 | 81 | 82 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/res/layout/outline_center_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/outline_item_view_date.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/outline_item_view_note.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 29 | 30 | 37 | 38 | 50 | 51 | 62 | 63 | 64 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/outline_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 19 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/outline_toolbar2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 17 | 18 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_compose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_compose.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_attach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_attach.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_back.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_backward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_backward.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_check.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_edit.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_error.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_format_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_format_off.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_forward.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_loading.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_more.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_redo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_redo_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_redo_disabled.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_tick.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_uncheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_uncheck.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_undo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_note_edit_undo_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xhdpi/ic_note_edit_undo_disabled.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_note_edit_format_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xxhdpi/ic_note_edit_format_on.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | #2196F3 18 | 4dp 19 | 0 20 | 100 21 | false 22 | false 23 | 4000 24 | 5000 25 | 500 26 | 3 27 | -90 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #01A92D 4 | #01A92D 5 | #01A92D 6 | #5A5A5A 7 | #FFF 8 | #01A92D 9 | #414141 10 | #f5f595 11 | #01A92D 12 | #5A5A5A 13 | #000 14 | #5A5A5A 15 | #5A5A5A 16 | #5A5A5A 17 | #F8F8F8 18 | #FFFFFF 19 | #EBEBEB 20 | #979797 21 | #000 22 | #000 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 80dp 4 | 50dp 5 | 30dp 6 | 150dp 7 | 50dp 8 | 10dp 9 | 5dp 10 | 15dp 11 | 20dp 12 | 50dp 13 | 20dp 14 | 50dp 15 | 30dp 16 | 15sp 17 | 3dp 18 | 30dp 19 | 80dp 20 | 20sp 21 | 12sp 22 | 16sp 23 | 13sp 24 | 3dp 25 | 50dp 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SRich 3 | 所有笔记 4 | 删除 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 21 | 22 | 32 | 33 | 37 | 38 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/test/java/com/jscheng/srich/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.srich; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { url 'https://maven.google.com' } 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.1.2' 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChyengJason/SRich/f748baa5cb8403cd39e5697e483b4a20e13ba082/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 20 17:52:01 CST 2019 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-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /processor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /processor/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | implementation 'com.google.auto.service:auto-service:1.0-rc2' 6 | implementation 'com.squareup:javapoet:1.9.0' 7 | compile project(path: ':annotation') 8 | } 9 | 10 | sourceCompatibility = "1.8" 11 | targetCompatibility = "1.8" 12 | -------------------------------------------------------------------------------- /processor/src/main/java/com/jscheng/processor/AnnotatedClass.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.processor; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.JavaFile; 5 | import com.squareup.javapoet.MethodSpec; 6 | import com.squareup.javapoet.ParameterSpec; 7 | import com.squareup.javapoet.ParameterizedTypeName; 8 | import com.squareup.javapoet.TypeName; 9 | import com.squareup.javapoet.TypeSpec; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Map; 13 | 14 | import javax.lang.model.element.Modifier; 15 | import javax.lang.model.element.TypeElement; 16 | import javax.lang.model.util.Elements; 17 | 18 | /** 19 | * Created By Chengjunsen on 2019/3/18 20 | */ 21 | public class AnnotatedClass { 22 | private static final String CLASS_PAKCAGE = "com.jscheng.processor"; 23 | private static final String CLASS_ROOT_NAME = "Router$$"; 24 | private static final String LOAD_METHOD = "load"; 25 | private static final ClassName IROUTE_INFO = ClassName.get("com.jscheng.srich.route", "IRouteInfo"); 26 | 27 | private TypeElement mTypeElement; 28 | private ArrayList mRouteClasses; 29 | private Elements mElements; 30 | 31 | AnnotatedClass(TypeElement typeElement, Elements elements) { 32 | mTypeElement = typeElement; 33 | mElements = elements; 34 | mRouteClasses = new ArrayList<>(); 35 | } 36 | 37 | void addField(RouteClass field) { 38 | mRouteClasses.add(field); 39 | } 40 | 41 | JavaFile generateFile() { 42 | // 生成参数 Map> routes> 43 | ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get( 44 | ClassName.get(Map.class), 45 | ClassName.get(String.class), 46 | ClassName.get(Class.class) 47 | ); 48 | 49 | ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, "routes").build(); 50 | 51 | MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(LOAD_METHOD) 52 | .addModifiers(Modifier.PUBLIC) 53 | .addAnnotation(Override.class) 54 | .addParameter(parameter) 55 | .returns(TypeName.VOID); 56 | 57 | for (RouteClass item: mRouteClasses) { 58 | // uri class 59 | methodBuilder.addStatement("routes.put($S, $T.class)", item.getUrl(), item.getTypeClass()); 60 | } 61 | 62 | TypeSpec injectClass = TypeSpec.classBuilder(CLASS_ROOT_NAME + mTypeElement.getSimpleName()) 63 | .addModifiers(Modifier.PUBLIC) 64 | .addSuperinterface(IROUTE_INFO) 65 | .addMethod(methodBuilder.build()) 66 | .build(); 67 | 68 | return JavaFile.builder(CLASS_PAKCAGE, injectClass).build(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /processor/src/main/java/com/jscheng/processor/RouteClass.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.processor; 2 | 3 | import com.jscheng.annotations.Route; 4 | 5 | import javax.lang.model.element.Element; 6 | import javax.lang.model.element.ElementKind; 7 | import javax.lang.model.element.Name; 8 | import javax.lang.model.element.TypeElement; 9 | import javax.lang.model.type.TypeMirror; 10 | 11 | /** 12 | * Created By Chengjunsen on 2019/3/18 13 | */ 14 | public class RouteClass { 15 | private TypeElement mTypeElement; 16 | private String mUrl; 17 | 18 | RouteClass(TypeElement element) throws IllegalArgumentException { 19 | 20 | if (element.getKind() != ElementKind.CLASS) { 21 | throw new IllegalArgumentException(String.format("Only classes can be annotated with @%s", 22 | Route.class.getSimpleName())); 23 | } 24 | 25 | mTypeElement = element; 26 | 27 | Route route = mTypeElement.getAnnotation(Route.class); 28 | mUrl = route.value(); 29 | 30 | if (mUrl.isEmpty()) { 31 | throw new IllegalArgumentException(String.format("value() in %s for class %s is not valid !", 32 | Route.class.getSimpleName(), 33 | mTypeElement.getSimpleName())); 34 | } 35 | } 36 | 37 | 38 | TypeElement getTypeClass() { 39 | return mTypeElement; 40 | } 41 | 42 | String getUrl() { 43 | return mUrl; 44 | } 45 | 46 | TypeMirror getFieldType() { 47 | return mTypeElement.asType(); 48 | } 49 | 50 | public Name getClassName() { 51 | return mTypeElement.getSimpleName(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /processor/src/main/java/com/jscheng/processor/RouterProcessor.java: -------------------------------------------------------------------------------- 1 | package com.jscheng.processor; 2 | 3 | import com.google.auto.service.AutoService; 4 | import com.jscheng.annotations.Route; 5 | import com.squareup.javapoet.JavaFile; 6 | 7 | import java.io.IOException; 8 | import java.util.LinkedHashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.TreeMap; 12 | 13 | import javax.annotation.processing.AbstractProcessor; 14 | import javax.annotation.processing.Filer; 15 | import javax.annotation.processing.Messager; 16 | import javax.annotation.processing.ProcessingEnvironment; 17 | import javax.annotation.processing.Processor; 18 | import javax.annotation.processing.RoundEnvironment; 19 | import javax.lang.model.SourceVersion; 20 | import javax.lang.model.element.Element; 21 | import javax.lang.model.element.PackageElement; 22 | import javax.lang.model.element.TypeElement; 23 | import javax.lang.model.util.Elements; 24 | import javax.tools.Diagnostic; 25 | 26 | /** 27 | * Created By Chengjunsen on 2019/3/18 28 | */ 29 | @AutoService(Processor.class) 30 | public class RouterProcessor extends AbstractProcessor { 31 | 32 | private Filer mFiler; 33 | 34 | private Elements mElements; 35 | 36 | private Messager mMessager; 37 | 38 | private Map mAnnotatedClassMap; 39 | 40 | @Override 41 | public synchronized void init(ProcessingEnvironment processingEnvironment) { 42 | super.init(processingEnvironment); 43 | mElements = processingEnvironment.getElementUtils(); 44 | mMessager = processingEnvironment.getMessager(); 45 | mFiler = processingEnvironment.getFiler(); 46 | mAnnotatedClassMap = new TreeMap<>(); 47 | } 48 | 49 | @Override 50 | public boolean process(Set set, RoundEnvironment roundEnvironment) { 51 | mAnnotatedClassMap.clear(); 52 | 53 | processRoute(roundEnvironment); 54 | 55 | for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) { 56 | try { 57 | JavaFile javaFile = annotatedClass.generateFile(); 58 | info(javaFile.toString()); 59 | javaFile.writeTo(mFiler); 60 | } catch (IOException e) { 61 | error("Generate file failed, reason: %s", e.getMessage()); 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | private void processRoute(RoundEnvironment roundEnv) throws IllegalArgumentException { 68 | for (Element element : roundEnv.getElementsAnnotatedWith(Route.class)) { 69 | TypeElement typeElement = (TypeElement) element; 70 | PackageElement packageElement = (PackageElement) element.getEnclosingElement(); 71 | 72 | String fullName = typeElement.getQualifiedName().toString(); 73 | 74 | AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullName); 75 | 76 | if (annotatedClass == null) { 77 | annotatedClass = new AnnotatedClass(typeElement, mElements); 78 | mAnnotatedClassMap.put(fullName, annotatedClass); 79 | } 80 | 81 | RouteClass routeClass = new RouteClass(typeElement); 82 | info(routeClass.getClassName() + " " + routeClass.getUrl()); 83 | annotatedClass.addField(routeClass); 84 | } 85 | } 86 | 87 | @Override 88 | public Set getSupportedAnnotationTypes() { 89 | Set set = new LinkedHashSet<>(); 90 | set.add(Route.class.getCanonicalName()); 91 | return set; 92 | } 93 | 94 | @Override 95 | public SourceVersion getSupportedSourceVersion() { 96 | return SourceVersion.latestSupported(); 97 | } 98 | 99 | private void error(String msg, Object... args) { 100 | mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args)); 101 | } 102 | 103 | private void info(String msg, Object... args) { 104 | mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 之前在看大部分的Android 富文本编辑几乎都是利用webview实现,所以,便有了做一个Android原生的富文本编辑器的主意。 2 | 3 | ##### Blog 4 | 5 | [Android 写一个属于自己的富文本编辑器](https://blog.csdn.net/qq_15893929/article/details/88670198) 6 | 7 | ##### 样例 8 | 照惯例先秀一下图: 9 | 10 | 11 |     12 | 13 |     14 | 15 | 16 | 该富文本编辑器样式仿照印象笔记的Android版,绘制层实现基于Android的span样式。 17 | 18 | ##### 目前已经实现的功能: 19 | 1. 粗体、斜体、下划线、删除线、上下标、背景色字体样式; 20 | 2. 分割线、缩进、有序列表、无序列表、复选框行样式; 21 | 3. 支持插入本地图片; 22 | 4. 支持插入网络图片; 23 | 4. 支持图片预览; 24 | 5. 支持撤销和反撤销; 25 | 6. 支持本地持久化、支持增删改; 26 | 7. 支持编辑模式和预览模式 27 | 28 | ##### 具体的实现: 29 | 主要的实现在于编辑页面,直接是继承自EditText加以改造的(偷懒),但是如果想实现一个商业级别的编辑器,建议使用StaticLayout和自定义View,但是需要考虑的东西比较多,例如输入法和排版布局、选区管理绘制、各类点击事件。 30 | 31 | ![大致模块](https://img-blog.csdnimg.cn/20190319203551373.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzE1ODkzOTI5,size_16,color_FFFFFF,t_70) 32 | * NoteEditText 继承自 EditText,NoteEditorManager管理基本逻辑; 33 | 34 | * NoteEditorRender负责绘制,NoteLineSpanRender是行样式、NoteWordSpanRender是字体样式; 35 | 36 | * NoteRevocationManager负责撤销与反撤销; 37 | 38 | * NoteImageLoader 是图片加载库,之前想用Glide库,但是Glide不支持直接在UI线程获取缓冲区的Bitmap,所以简单写了一个基于OkHttp的图片加载,内部参照(抄)了Glide的思想,例如ImageView在宽高为0时如何加载图片、图片过大时候怎么处理。Glide太强大了,代码也好复杂; 39 | 后续还是要继续替换成Glide,可以通过自定义设置Glide缓冲池,这样外部就可以直接拿到缓冲区数据; 40 | 41 | * converter 是简单地将富文本对象转成文本数据,或将文本数转成富文本对象的模块; 42 | 43 | * dao 数据库层; 44 | 45 | * route 是在利用APT和借鉴OkHttp责任链模式仿写的一个跳转路由的功能; 46 | 只是自己学习所写的一个小工具,完全可以去掉。 47 | 48 | 49 | ##### 后续计划: 50 | 51 | 这个版本更多的是将自己所学的一些知识的运用,只做了小一段时间,所以存留很多了bug和缺陷,后续会继续找时间修补。 52 | 53 | 想增加的内容: 54 | 1. 增加导入导出html 55 | 2. 完善图片池 56 | 3. 增加桌面小部件 57 | 4. 增加保存为图片 58 | 5. 支持超链接、引用更多样式 59 | 60 | 61 | ##### 附上 62 | 1. BackgroundColorSpan 背景色 63 | 2. ForegroundColorSpan 文本颜色(前景色) 64 | 3. RasterizerSpan 光栅效果 65 | 4. StrikethroughSpan 删除线 66 | 5. SuggestionSpan 相当于占位符 67 | 6. UnderlineSpan 下划线 68 | 7. AbsoluteSizeSpan 绝对大小(文本字体) 69 | 8. DynamicDrawableSpan 设置图片,基于文本基线或底部对齐。 70 | 9. ImageSpan 图片 71 | 10. RelativeSizeSpan 相对大小(文本字体) 72 | 11. ReplacementSpan 父类,一般不用 73 | 12. URLSpan 文本超链接 74 | 13. StyleSpan 字体样式 75 | 14. SubscriptSpan 下标 76 | 15. SuperscriptSpan 上标 77 | 16. TextAppearanceSpan 文本外貌(包括字体、大小、样式和颜色) 78 | 17. TypefaceSpan 文本字体 79 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':annotation', ':processor' 2 | --------------------------------------------------------------------------------