本文最后更新于:2019年8月2日 下午
SD - Slam Dump(并不是)
这个App的主要目的是满足广大人民群众对图片编辑的需求。
字体问题Android默认的字体不太好看,也不一定能很好地匹配背景图。如果内置字体,遇到最大的问题是版权问题。 因此决定增加用户自行导入字体的功能,由用户来决定使用什么字体。
原来的字体文件是放在asset中。Typeface.createFromAsset
直接引入并使用。
1 2 Typeface tf = Typeface.createFromAsset(mgr, "fonts/fz_grid.ttf" ); mContentTv.setTypeface(tf);
JAVA
设计一个字体管理界面。用户自行选择将字体文件复制到App内部存储路径。 使用字体时,再用Typeface.createFromFile()
获取Typeface。
选择文件调用系统文件选择器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private static final int REQ_CODE_CHOOSE_FILE = 10 ; Intent intent = new Intent (Intent.ACTION_GET_CONTENT); intent.setType("*/*" ); startActivityForResult(intent, REQ_CODE_CHOOSE_FILE); @Override protected void onActivityResult (int requestCode, int resultCode, @Nullable Intent data) { switch (requestCode) { case REQ_CODE_CHOOSE_FILE: if (data != null ) { Uri uri = data.getData(); Log.d(TAG, "onActivityResult: uri: " + uri); if (uri != null && !TextUtils.isEmpty(uri.getPath())) { copyFile(uri); } else { Log.e(TAG, "onActivityResult: 选择的文件无效" ); } } else { showShort(getApplicationContext(), "没选中文件" ); Log.e(TAG, "onActivityResult: data is NULL 没选中文件" ); } break ; default : super .onActivityResult(requestCode, resultCode, data); break ; } }
JAVA
处理uriuri形如
content://com.android.externalstorage.documents/document/primary%3ADownload%2Ffz_grid.ttf
uri.getPath获取到的并不是文件的绝对路径。但我们可以利用ContentResolver来获取到InputStream。 也可以获取到uri的文件名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 private void copyFile (final Uri uri) { mAddIv.setClickable(false ); Animation rotate = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.rotate_scan); rotate.setDuration(400 ); mAddIv.startAnimation(rotate); new Thread (new Runnable () { @Override public void run () { try { Cursor cursor = getContentResolver().query(uri, null , null , null , null ); int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); cursor.moveToFirst(); String name = cursor.getString(nameIndex); cursor.close(); InputStream fis = getContentResolver().openInputStream(uri); File outputFile = new File (TypefaceStore.getStorePath(getApplicationContext()), name); if (outputFile.exists()) { boolean d = outputFile.delete(); Log.d(TAG, "删除旧文件: " + d); } boolean n = outputFile.createNewFile(); Log.d(TAG, "copyFile: 新建文件 " + n); FileOutputStream fos = new FileOutputStream (outputFile); byte [] tmp = new byte [2048 ]; int i; while ((i = fis.read(tmp)) != -1 ) { fos.write(tmp, 0 , i); } fos.flush(); fos.close(); fis.close(); } catch (Exception e) { Log.e(TAG, "copyFile ERROR:" , e); } } }).start(); }
JAVA
也可以简单地使用uri.getLastPathSegment来获取文件名
1 2 3 uri.getLastPathSegment(); String[] t = uriPath.split(File.separator);String name = t[t.length - 1 ];
JAVA
https://stackoverflow.com/questions/4263002/how-to-get-file-name-from-uri
使用toolbar时经常会遇到问题。例如设置title的问题 。
这里自己创建一个统一的标题栏TitleBar。想要什么控件自己添加。
Google MobileAdsMobileAds.initialize(getApplicationContext(), AdsMgr.GOOGLE_ADS_APP_ID);
的执行会占用很多时间。测试过程中发现小米手机甚至使用了3秒钟来执行这个方法。
https://stackoverflow.com/questions/37418663/what-is-the-proper-way-to-call-mobileads-initialize
给启动页Activity一个纯色的启动背景。
1 2 3 4 5 <style name ="AppTheme.NoActionBar" > <item name ="windowActionBar" > false</item > <item name ="windowNoTitle" > true</item > <item name ="android:windowBackground" > @color/colorPrimary</item > </style >
XML
启动页中初始化Ads时实在是耗时太长,干脆放到子线程中去操作。 虽然官方文档 建议的是越早初始化越好。但也不希望太影响用户体验。
递归查看某个路径下的文件1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private static void treeDir (File dir, int level) { StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < level; i++) { sb.append("-" ); } sb.append(" " ); if (dir.isDirectory()) { level++; for (File f : dir.listFiles()) { treeDir(f, level); } } else { } }
JAVA
提供草稿功能为方便用户使用,提供草稿功能。这就涉及到增删查改的操作。
[2019-7-31] 本来想直接用sqlite,但为了开发方便,选用了greenDAO 。
https://github.com/greenrobot/greenDAO
使用2个表,分别为Draft(存档)和DraftContent(图层)。DraftContent中存放着关联的存档ID。
能保存的东西都保存下来。
greendao插入元素1 2 3 4 5 6 7 Draft draft1 = genDraft("示例1" , p1Path);Draft draft2 = genDraft("示例2" , p2Path);Draft draft3 = genDraft("示例3" , p3Path); Log.d(TAG, "addDemoDraft: id: " + draft1.getDraftId() + "," + draft3.getDraftId()); daoSession.insert(draft1); daoSession.insert(draft2); daoSession.insert(draft3);
JAVA
插入元素后就有id了。
greendao删除元素1 2 3 4 5 6 7 8 9 10 11 DraftDao draftDao = daoSession.getDraftDao();DraftContentDao draftContentDao = daoSession.getDraftContentDao();for (Draft d : drafts) { Log.d(TAG, "删除 " + d.getName()); draftDao.queryBuilder() .where(DraftDao.Properties.DraftId.eq(d.getDraftId())).buildDelete() .executeDeleteWithoutDetachingEntities(); draftContentDao.queryBuilder() .where(DraftContentDao.Properties.RelativeDraftId.eq(d.getDraftId())).buildDelete() .executeDeleteWithoutDetachingEntities(); }
JAVA
使用DrawerLayout报错: IllegalArgumentException: No drawer view found with gravity LEFT
1 2 3 java.lang .IllegalArgumentException : No drawer view found with gravity LEFT at androidx.drawerlayout .widget .DrawerLayout .openDrawer (DrawerLayout.java :1736 ) at androidx.drawerlayout .widget .DrawerLayout .openDrawer (DrawerLayout.java :1722 )
STYLUS
忘记中xml中加上开抽屉方向了 tools:openDrawer=”start”
1 2 3 4 5 6 7 8 <androidx.drawerlayout.widget.DrawerLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" android:id ="@+id/main_page_root" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".act.MainActivity" tools:openDrawer ="start" >
XML
抽屉加上方向 android:layout_gravity=”start”
1 2 3 4 5 6 7 <LinearLayout android:layout_width ="match_parent" android:layout_height ="match_parent" android:layout_gravity ="start" android:layout_marginEnd ="100dp" android:orientation ="vertical" >
XML
美术设计,App交互设计设计是一个比较令我头疼的问题。在这个看脸的时代,App一定要好看!对我而言,直接采用material design的风格会比较省事。 经过调整和对比,我选择使用暗色的风格。因为现在主流的图形编辑软件,颜色风格以暗色居多。
参考:
文字编辑文字内容,大小,旋转方向,颜色都可以调整。
需要一个调色盘来调整颜色。找个第三方的,好看能用即可。
删除存档报错list类的经典异常 ConcurrentModificationException。
1 2 java.util .ConcurrentModificationException at java.util .ArrayList$Itr .next (ArrayList.java :860 )
STYLUS
list删除元素时报错。这样写是不行的。
1 2 3 4 5 for (Data d : dataList) { if (d.selected) { dataList.remove(d); } }
JAVA
用迭代器来删除元素。
1 2 3 4 5 6 7 Iterator<Data> iterator = dataList.iterator();while (iterator.hasNext()) { Data data = iterator.next(); if (data.selected) { iterator.remove(); } }
JAVA
输出图片 保存View的显示内容获取一个view的bitmap,然后保存到文件去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private Bitmap getCacheBitmapFromView (View view) { final boolean drawingCacheEnabled = true ; view.setDrawingCacheEnabled(drawingCacheEnabled); view.buildDrawingCache(drawingCacheEnabled); final Bitmap drawingCache = view.getDrawingCache(); Bitmap bitmap; if (drawingCache != null ) { bitmap = Bitmap.createBitmap(drawingCache); view.setDrawingCacheEnabled(false ); } else { bitmap = null ; } return bitmap; }public static boolean saveBitmapFile (Bitmap bitmap, String fileAbsPath) { File file = new File (fileAbsPath); try { if (file.exists()) { file.delete(); } BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream (file)); bitmap.compress(Bitmap.CompressFormat.JPEG, 100 , bos); bos.flush(); bos.close(); } catch (IOException e) { e.printStackTrace(); return false ; } return true ; }
JAVA
保存图片文件后的处理用户输出图片文件后,打开微信想发送这张图片。但是用户发现微信的快捷发送功能找不到这张图片。 怎么才能让微信知道这里新增了一张图片呢?
如果要发送广播ACTION_MEDIA_MOUNTED
1 sendBroadcast(new Intent (Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(outputFile)));
JAVA
报错,没有足够的权限
1 java.lang .SecurityException : Permission Denial: not allowed to send broadcast android.intent .action .MEDIA_MOUNTED
STYLUS
Android KK开始,这个广播开始只能由系统发出。KK及之后的版本需使用Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
1 2 File outputFile = new File (filePath); sendBroadcast(new Intent (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(outputFile)));
JAVA
参考
https://stackoverflow.com/questions/24072489/java-lang-securityexception-permission-denial-not-allowed-to-send-broadcast-an
移动TextView编辑页中有一个需求是手指拖动文字。
1.1.x版本1.1.0版本的做法是,在Activity的onTouch方法里来改变TextView的坐标。从而实现TextView的拖动效果。 父View和子View设同一个OnTouchListener。但是只有父view来处理触摸事件。 如果是子view接收到了触摸事件,则做一个bool标记firstOnTv = true,返回false,把触摸事件交给父view来处理。 父view处理触摸事件时,判断如果刚才点中的是子view(即mContentTv),则在MotionEvent.ACTION_MOVE时更改子view的坐标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 private View.OnTouchListener mWsOnTouchListener = new View .OnTouchListener() { boolean firstOnTv = false ; float originTvX; float originTvY; float downX; float downY; @Override public boolean onTouch (View v, MotionEvent event) { final int id = v.getId(); float x = event.getX(); float y = event.getY(); if (event.getAction() == MotionEvent.ACTION_DOWN) { mSaveIv.setEnabled(true ); if (id == mContentTv.getId()) { firstOnTv = true ; originTvX = mContentTv.getX(); originTvY = mContentTv.getY(); } downX = event.getX(); downY = event.getY(); return id != mContentTv.getId(); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (!firstOnTv) { return true ; } float dx = x - downX; float dy = y - downY; if (Math.abs(dx) > 2 && Math.abs(dy) > 2 ) { mContentTv.setX(originTvX + dx); mContentTv.setY(originTvY + dy); } return true ; } else if (event.getAction() == MotionEvent.ACTION_UP) { firstOnTv = false ; if (mCanvasWid > 0 && mCanvasHeight > 0 ) { mDraftContent.setTvLocationXRatio(mContentTv.getX() / mCanvasWid); mDraftContent.setTvLocationYRatio(mContentTv.getY() / mCanvasHeight); } return true ; } return false ; } };
JAVA
版本更新
2019-8-8 v1.1.1 版本更新
解决了一些bug
UI调整,增加了抽屉的头图和欢迎文字
2019-8-4 v1.1.0 版本更新