前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「SD编辑」开发记录

「SD编辑」开发记录

作者头像
AnRFDev
发布2021-02-01 15:17:36
6730
发布2021-02-01 15:17:36
举报
文章被收录于专栏:AnRFDev

SD - Slam Dump(并不是)

这个App的主要目的是满足广大人民群众对图片编辑的需求。

字体问题

Android默认的字体不太好看,也不一定能很好地匹配背景图。如果内置字体,遇到最大的问题是版权问题。 因此决定增加用户自行导入字体的功能,由用户来决定使用什么字体。

原来的字体文件是放在asset中。Typeface.createFromAsset直接引入并使用。

代码语言:javascript
复制
Typeface tf = Typeface.createFromAsset(mgr, "fonts/fz_grid.ttf");
mContentTv.setTypeface(tf);

设计一个字体管理界面。用户自行选择将字体文件复制到App内部存储路径。

使用字体时,再用Typeface.createFromFile()获取Typeface。

选择文件

调用系统文件选择器

代码语言:javascript
复制
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;
        }
    }

处理uri

uri形如

content://com.android.externalstorage.documents/document/primary%3ADownload%2Ffz_grid.ttf

uri.getPath获取到的并不是文件的绝对路径。但我们可以利用ContentResolver来获取到InputStream。 也可以获取到uri的文件名。

代码语言:javascript
复制
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();

}

也可以简单地使用uri.getLastPathSegment来获取文件名

代码语言:javascript
复制
uri.getLastPathSegment();
String[] t = uriPath.split(File.separator);
String name = t[t.length - 1];

https://stackoverflow.com/questions/4263002/how-to-get-file-name-from-uri

Toolbar问题

使用toolbar时经常会遇到问题。例如设置title的问题

这里自己创建一个统一的标题栏TitleBar。想要什么控件自己添加。

Google MobileAds

MobileAds.initialize(getApplicationContext(), AdsMgr.GOOGLE_ADS_APP_ID);的执行会占用很多时间。测试过程中发现小米手机甚至使用了3秒钟来执行这个方法。

https://stackoverflow.com/questions/37418663/what-is-the-proper-way-to-call-mobileads-initialize

给启动页Activity一个纯色的启动背景。

代码语言:javascript
复制
<style name="AppTheme.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
    <item name="android:windowBackground">@color/colorPrimary</item>
</style>

启动页中初始化Ads时实在是耗时太长,干脆放到子线程中去操作。

虽然官方文档建议的是越早初始化越好。但也不希望太影响用户体验。

递归查看某个路径下的文件

代码语言:javascript
复制
    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()) {
//            LL.d(TAG, sb.toString() + dir.getName());
            level++;
            for (File f : dir.listFiles()) {
                treeDir(f, level);
            }
        } else {
//            LL.d(TAG, sb.toString() + dir.getName());
        }
    }

提供草稿功能

为方便用户使用,提供草稿功能。这就涉及到增删查改的操作。

[2019-7-31] 本来想直接用sqlite,但为了开发方便,选用了greenDAO

https://github.com/greenrobot/greenDAO

使用2个表,分别为Draft(存档)和DraftContent(图层)。DraftContent中存放着关联的存档ID。

能保存的东西都保存下来。

greendao插入元素

代码语言:javascript
复制
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);

插入元素后就有id了。

greendao删除元素

代码语言:javascript
复制
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();
}

使用DrawerLayout

报错: IllegalArgumentException: No drawer view found with gravity LEFT

代码语言:javascript
复制
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)

忘记中xml中加上开抽屉方向了 tools:openDrawer=”start”

代码语言:javascript
复制
<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">

抽屉加上方向 android:layout_gravity=”start”

代码语言:javascript
复制
<!-- 抽屉 -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:layout_marginEnd="100dp"
    android:orientation="vertical">

美术设计,App交互设计

设计是一个比较令我头疼的问题。在这个看脸的时代,App一定要好看!对我而言,直接采用material design的风格会比较省事。 经过调整和对比,我选择使用暗色的风格。因为现在主流的图形编辑软件,颜色风格以暗色居多。

参考:

文字编辑

文字内容,大小,旋转方向,颜色都可以调整。

需要一个调色盘来调整颜色。找个第三方的,好看能用即可。

删除存档报错

list类的经典异常 ConcurrentModificationException。

代码语言:javascript
复制
java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.next(ArrayList.java:860)

list删除元素时报错。这样写是不行的。

代码语言:javascript
复制
for (Data d : dataList) {
    if (d.selected) {
        dataList.remove(d);
    }
}

用迭代器来删除元素。

代码语言:javascript
复制
Iterator<Data> iterator = dataList.iterator();
while (iterator.hasNext()) {
    Data data = iterator.next();
    if (data.selected) {
        iterator.remove();
    }
}

输出图片

保存View的显示内容

获取一个view的bitmap,然后保存到文件去。

代码语言:javascript
复制
/**
 * 获取一个 View 的缓存视图
 */
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;
}

保存图片文件后的处理

用户输出图片文件后,打开微信想发送这张图片。但是用户发现微信的快捷发送功能找不到这张图片。 怎么才能让微信知道这里新增了一张图片呢?

如果要发送广播ACTION_MEDIA_MOUNTED

代码语言:javascript
复制
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(outputFile)));

报错,没有足够的权限

代码语言:javascript
复制
java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.intent.action.MEDIA_MOUNTED

Android KK开始,这个广播开始只能由系统发出。KK及之后的版本需使用Intent.ACTION_MEDIA_SCANNER_SCAN_FILE

代码语言:javascript
复制
File outputFile = new File(filePath);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(outputFile)));

参考

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的坐标。

代码语言:javascript
复制
    private View.OnTouchListener mWsOnTouchListener = new View.OnTouchListener() {

        boolean firstOnTv = false; // 最开始点中的是tv
        float originTvX;           // tv最开始的坐标
        float originTvY;

        float downX;
        float downY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            final int id = v.getId();
//            Log.d(TAG, "onTouch: touch tv: " + (id == mContentTv.getId()) + ", touch ws: " + (id == mWorkspaceField.getId()));
            float x = event.getX();
            float y = event.getY();
//            Log.d(TAG, "onTouch: [" + x + ", " + y + "] , " + event);
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mSaveIv.setEnabled(true);
                if (id == mContentTv.getId()) {
                    firstOnTv = true;
                    originTvX = mContentTv.getX();
                    originTvY = mContentTv.getY();
//                    Log.d(TAG, "onTouch: 保存tv坐标 (" + originTvX + ", " + originTvY + ")");
                }
                downX = event.getX();
                downY = event.getY();
//                Log.d(TAG, "onTouch: down: x,y [" + x + ", " + y + "]");
                return id != mContentTv.getId();
            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
//                Log.d(TAG, "onTouch: move: x,y [" + x + ", " + y + "]");
                if (!firstOnTv) {
                    return true; // 不移动tv,直接消耗掉这个操作
                }
                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;
        }
    };

版本更新

  • 2019-8-8 v1.1.1 版本更新
    • 解决了一些bug
    • UI调整,增加了抽屉的头图和欢迎文字
  • 2019-8-4 v1.1.0 版本更新
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 字体问题
  • 选择文件
  • 处理uri
  • Toolbar问题
  • Google MobileAds
  • 递归查看某个路径下的文件
  • 提供草稿功能
    • greendao插入元素
      • greendao删除元素
      • 使用DrawerLayout
      • 美术设计,App交互设计
      • 文字编辑
      • 删除存档报错
      • 输出图片
        • 保存View的显示内容
          • 保存图片文件后的处理
          • 移动TextView
            • 1.1.x版本
            • 版本更新
            相关产品与服务
            图片处理
            图片处理(Image Processing,IP)是由腾讯云数据万象提供的丰富的图片处理服务,广泛应用于腾讯内部各产品。支持对腾讯云对象存储 COS 或第三方源的图片进行处理,提供基础处理能力(图片裁剪、转格式、缩放、打水印等)、图片瘦身能力(Guetzli 压缩、AVIF 转码压缩)、盲水印版权保护能力,同时支持先进的图像 AI 功能(图像增强、图像标签、图像评分、图像修复、商品抠图等),满足多种业务场景下的图片处理需求。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档