开发实战过程中遇到的细节问题(一)

获取系统时间的24小时制与12小时制

最近在做项目的时候发生了一点错误,服务器端是24小时制的时间,而本地数据库则是12小时制的时间

1、获取24小时制的时间

public static String showDate() {
    SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String date = sDateFormat.format(new Date());
    return date;
}
  • 1
  • 2
  • 3
  • 4
  • 5

2、获取12小时制的时间

public static String showDate() {
    SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    String date = sDateFormat.format(new Date());
    return date;
}
  • 1
  • 2
  • 3
  • 4
  • 5

两者区别就在于HH和hh,可以看下文档的其他示例

ViewPager中的Fragment在切换时不重新加载

最近在做项目的时候发生了一点错误,在ViewPager中的Fragment中切换到第三页重新切回第一页时,会重新加载第一页,显得页面一直在加载,降低了用户体验,因为viewPager会事先加载好当前页的前后两页,也就是到了第三页的时候,第一页已经被销毁了,回到第二页的时候会重新创建

解决方法

//默认是1
mViewPager.setOffscreenPageLimit(3);  
  • 1
  • 2

优化购物车,选中物品时价钱相加减的精确运算

做到商品购物车模块的时候,发现价钱的加减并不能单纯的使用+、-来实现,由于我们的价格都是double类型的,如10.24元,相互加减的时候会出现20.45555555的情况,所以我们需要使用到API中BigDecimal这个类进行包装,然后运算

代码如下:

public void selectSingle() {
    //创建BigDecimal对象
    BigDecimal bj1 = new BigDecimal(Double.toString(money1));
    BigDecimal bj2 = new BigDecimal(Double.toString(money2));
    if (selected_Id.contains(shop.get_id())) {//相减
        sum_money = bj1.subtract(bj2).doubleValue();
    } else {//相加
        sum_money = bj1.add(bj2).doubleValue();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

效果如下:可以看到价钱已经是正常的加减了

这里写图片描述

扫描二维码关注公众号,回复: 1856259 查看本文章

启动模式的细节

情景发生:当我们应用程序按Home键返回桌面时,再次点击应用程序不能恢复到离开时的Activity

解决方案:只要该栈中之前的任何一个Activity在manifest文件中定义了启动模式为singleTask,那么再次点击应用时会启动第一个Activity,只要去除之前Activity中singleTask属性就能恢复回离开时的Activity

通过广播监听网络的变化清况

通常在使用软件的时候会出现网路变化的情况,比如Wifi断线导致使用流量上网,这个时候作为我们的软件就必须通知用户在使用流量上网。首先,Manifests中注册网络变化情况的广播

<!-- 广播 -->
<receiver android:name=".Receiver.NetReceiver">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接着,创建该Receiver,根据网络的变化情况进行相对应的提醒。这里当用户打开或关闭Wifi和移动数据时,该广播可以收到

public class NetReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //网络广播接收者
        if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
            ConnectivityManager connectivityManager = (ConnectivityManager)
                    context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo activeInfo = connectivityManager.getActiveNetworkInfo();
            NetworkInfo netInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
            NetworkInfo wifiInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (activeInfo != null) {
                //网络可用
                if (activeInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
                    //判断移动数据
                    if (netInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
                        Toast.makeText(context, "您正在使用移动数据", Toast.LENGTH_SHORT).show();
                    }
                    //判斷Wifi數據
                    if (wifiInfo.getState().equals(NetworkInfo.State.CONNECTED)) {
                        Toast.makeText(context, "您正在使用Wifi数据", Toast.LENGTH_SHORT).show();
                    }
                } else {
                    Toast.makeText(context, "请检查网络是否已联网", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
}
  • 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

优化Gradle,编译时间从33.8秒降到4.5秒

在项目的gradle中添加以下语句

 tasks.whenTaskAdded { task ->
    if (task.name.contains("lint")
            || task.name.equals("clean")
            || task.name.contains("Aidl")
            || task.name.contains("mockableAndroidJar")
            || task.name.contains("UnitTest")
            || task.name.contains("AndroidTest")
            || task.name.contains("Ndk")
            || task.name.contains("Jni")
    ) {
        task.enabled = false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Fragment的懒加载基类

在项目中添加该基类,新的Fragment继承该BaseFragment就可以轻松实现Fragment的懒加载

public abstract class BaseFragment extends Fragment implements View.OnClickListener {

    private boolean isPrepared;
    private boolean isVisible;

    public abstract View initViews(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);

    public abstract void initData();

    public abstract void initListener();

    public abstract void processClick(View v);

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (getUserVisibleHint()) {
            isVisible = true;
            lazyLoad();
        } else {
            isVisible = false;
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return initViews(inflater, container, savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isPrepared = true;
        lazyLoad();
    }

    /**
     * 懒加载
     */
    private void lazyLoad() {
        if (!isVisible || !isPrepared) {
            return;
        }
        //加载数据
        initData();
        initListener();
    }


    @Override
    public void onClick(View v) {
        processClick(v);
    }
}
  • 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
  • 53
  • 54

优化错误,不让错误导致程序崩溃

在项目中,常常会因为list.get(0)获取没有数据而导致程序崩溃,这个时候应该把程序try-catch起来,让程序报错但不崩溃,比如

public Drawable getDrawable(String name) {
    try {
        return mResources.getDrawable(mResources.getIdentifier(name, "drawable", mPkgName));
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Context的应用场景

这里写图片描述

解决PhotoView在ViewPager中多点触摸时,报错崩溃的方法

使用PhotoView在ViewPager中展示出多图操作,如果操作过于频繁,那么下面这个错误

java.lang.IllegalArgumentException: pointerIndex out of range  
  • 1

其解决方法就是在PhotoView所在的Activity中添加下面的处理即可

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    try {
        return super.dispatchTouchEvent(ev);
    } catch (IllegalArgumentException ex) {
        ex.printStackTrace();
    }
    return false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用Html.fromhtml显示图片

在项目开发中,后台常常会使用这样的编辑器来编辑图文

这里写图片描述

返回在android端是一串html字符串,这个时候单单靠Html.fromhtml是显示不出来图片的,图片将会被小方格替代,下面是我已经处理好的一个类,该类只要Html.fromhtml识别到图片就会回调该类的getDrawable()方法。该类兼容网络图片和服务器图片,兼容以下两种格式

服务器图片url:/upload/2017/05/04/590aaae1b0d4a.png 
网络图片url:https://p.ssl.qhimg.com/t0140ba2595bb1b8c4a.png

/**
 * @author 许英俊 2017/6/7
 */
public class ImageGetterImpl implements Html.ImageGetter {

    private int width, height;
    private TextView tv;
    private String html;
    private File file;

    public ImageGetterImpl(TextView tv, String html, int width, int height) {
        this.tv = tv;
        this.html = html;
        this.width = width - 80;
        this.height = height;
    }

    @Override
    public Drawable getDrawable(String source) {
        Drawable drawable = null;
        // 区分网络图片和自己服务器图片,如果是服务器图片,则加个根目录
        if (!source.startsWith("http")) {
            source = RequestCenter.ROOT_URL + source;
        }
        //获取图片后缀名作为文件名
        String[] fileName = source.split("/");
        file = new File(Environment.getExternalStorageDirectory(), fileName[fileName.length - 1]);
        // 判断是否以http开头
        if (source.startsWith("http")) {
            // 判断路径是否存在
            if (file.exists()) {
                // 存在即获取drawable
                drawable = Drawable.createFromPath(file.getAbsolutePath());
                // 根据屏幕的宽高比等于图片的宽高比
                height = (width) * drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth();
                drawable.setBounds(0, 0, width, height);
            } else {
                // 不存在即开启异步任务加载网络图片
                AsyncLoadNetworkPic networkPic = new AsyncLoadNetworkPic();
                networkPic.execute(source);
            }
        }
        return drawable;
    }

    public class AsyncLoadNetworkPic extends AsyncTask<String, Integer, Void> {
        @Override
        protected Void doInBackground(String... params) {
            // 加载网络图片
            loadNetPic(params);
            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            // 当执行完成后再次为其设置一次
            tv.setText(Html.fromHtml(html, new ImageGetterImpl(tv, html, width, height), null));
        }
        /**
         * 加载网络图片
         */
        private void loadNetPic(String... params) {
            String path = params[0];
            InputStream in = null;
            FileOutputStream out = null;
            try {
                URL url = new URL(path);
                HttpURLConnection connUrl = (HttpURLConnection) url.openConnection();
                connUrl.setConnectTimeout(10000);
                connUrl.setRequestMethod("GET");
                if (connUrl.getResponseCode() == 200) {
                    in = connUrl.getInputStream();
                    out = new FileOutputStream(file);
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = in.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                    }
                } else {
                    Log.e("TAG", connUrl.getResponseCode() + "");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  • 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
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

对应的在安卓端使用三个参数的方法进行解析html字符串,其中DensityUtils两个方法表示获取屏幕的宽和高

tv_content.setText(Html.fromHtml(data.getContent(),
                new ImageGetterImpl(tv_content, data.getContent(),
                        DensityUtils.getDisplayWidth(this),
                        DensityUtils.getDisplayHeight(this)), null));
  • 1
  • 2
  • 3
  • 4

在手机的显示效果如下,中间隔那么多行请忽略

这里写图片描述

网络缓存

打开缓存

try {
    File httpCacheDir = new File(context.getCacheDir(), "http");
    long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
    HttpResponseCache.install(httpCacheDir, httpCacheSize);
} catch (IOException e) {
    Log.i(TAG, "HTTP response cache installation failed:" + e);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

清除缓存

HttpResponseCache cache = HttpResponseCache.getInstalled();
if (cache != null) {
    cache.delete();
}
  • 1
  • 2
  • 3
  • 4

如果对于某个请求我不想用cache,则可以使用下面代码

connection.addRequestProperty("Cache-Control", "no-cache");
  • 1

我想用cache但是又怕服务器的数据已经发生改变了,则可以使用下面代码

connection.addRequestProperty("Cache-Control", "max-age=0");
  • 1

由于HttpResponseCache的API是Android4.0提供的,兼容Android4.0以下版本

try {
    File httpCacheDir = new File(context.getCacheDir(), "http");
    long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
    Class.forName("android.net.http.HttpResponseCache")
                 .getMethod("install", File.class, long.class)
                 .invoke(null, httpCacheDir, httpCacheSize);
    } catch (Exception httpResponseCacheNotAvailable) {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

屏幕像素的适配

这里写图片描述

一般我们开发的app不会去适配小屏幕的手机,普遍适配1080 x 1920的手机,通过图中可以看出其density密度为480,那么通过dp与px的运算公式(1dp x 像素密度 / 160 = 实际像素数)计算得出,dp与px的比例是1:3,1080 x 1920的手机就需要在UI设计图中,将px除于3,即可获得dp值

模拟Json数据接口

使用EasyMock的Api,特别简单,只需要输入模拟的Json数据,访问其Api就能获取Json数据

这里写图片描述

注销、切换账号时,顺带销毁主界面

我们在开发的时候,往往会遇到注销、切换账号等等功能,这个时候我们的主界面还没消失,但是我们又要跳转到登陆界面,让主界面自己销毁,这个时候该怎么办。想了很久,忘了还有一个广播可以实现这个需求,当我们注销时,我们可以通过发送广播退出主界面,这样在退出界面的时候就不会回到原先的主界面

//在主界面中动态注册广播
IntentFilter filter = new IntentFilter();
filter.addAction("com.hensen.exit.main");
registerReceiver(myReceiver, filter);
//创建广播接收者
private BroadcastReceiver myReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        MainActivity.this.finish();
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在另外的界面注销、或者切换账号的时候,发送广播,让主界面销毁

sendBroadcast(new Intent("com.hensen.exit.main"));
  • 1

安卓7.0照相机的适配

在安卓7.0中,官方对照相机的启动做了改变,开发者也要进行适配

1、首先需要在Manifest中声明Provider,注意:如果你引入的第三方图片选择库有开启的相机的Provider,那么此时会冲突

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="{你的应用的包名}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、创建xml目录和创建file_path.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/com.handsome.teacherproject/files/Pictures" />
</paths>
  • 1
  • 2
  • 3
  • 4

3、在代码中启动我们的相机,注意:只兼容7.0以上的相机,其他相机采用另一方法

private static int REQUEST_IMAGE_CAPTURE = 0x01;

//拍照后照片的路径
private String mCurrentPhotoPath;

/**
 * 开启系统相机
 */
private void startPhotoCapture() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    //判断是否有相机应用
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        File photoFile = null;
        try {
            //创建临时图片文件
            photoFile = createImageFile();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        if (photoFile != null) {
            //FileProvider使用 content:// Uri 代替了 file:// Uri
            Uri photoURI;
            if (Build.VERSION.SDK_INT >= 24) {
                photoURI = FileProvider.getUriForFile(this, "{你的应用的包名}.fileprovider", photoFile);
            } else {
                photoURI = Uri.fromFile(photoFile);
            }
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
}

/**
 * 创建Image的临时文件
 */
private File createImageFile() throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    //.getExternalFilesDir()方法可以获取到 SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    //创建临时文件,文件前缀不能少于三个字符,后缀如果为空默认未".tmp"
    File image = File.createTempFile(
            imageFileName,  /* 前缀 */
            ".jpg",         /* 后缀 */
            storageDir      /* 文件夹 */
    );
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}

/*
 * 在返回的结果中拿到图片
 */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        //mCurrentPhotoPath,这个全局变量就是我们拍照后的文件路径,直接想怎么用就怎么用
        //这个是高清图,不是缩略图,所以要上传服务器的时候记得压缩后上传

    }
}
  • 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
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

沉浸式状态栏和导航栏

适配4.4-6.0沉浸式状态栏和导航栏

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class BaseTranslucentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //版本[4.4,5.0),设置状态栏和导航栏为透明
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
    }

    @SuppressLint("NewApi")
    public void setOrChangeTranslucentColor(Toolbar toolbar, View bottomNavigationBar, int translucentPrimaryColor) {
        //版本[4.4,5.0),设置状态栏和导航栏为透明
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (toolbar != null) {
                LayoutParams params = toolbar.getLayoutParams();
                int statusBarHeight = getStatusBarHeight(this);
                params.height += statusBarHeight;
                toolbar.setLayoutParams(params);

                toolbar.setPadding(toolbar.getPaddingLeft(),
                        toolbar.getPaddingTop() + getStatusBarHeight(this),
                        toolbar.getPaddingRight(), toolbar.getPaddingBottom());

                toolbar.setBackgroundColor(translucentPrimaryColor);
            }
            if (bottomNavigationBar != null) {
                if (hasNavigationBarShow(getWindowManager())) {
                    LayoutParams p = bottomNavigationBar.getLayoutParams();
                    p.height += getNavigationBarHeight(this);
                    bottomNavigationBar.setLayoutParams(p);

                    bottomNavigationBar.setBackgroundColor(translucentPrimaryColor);
                }
            }
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setNavigationBarColor(translucentPrimaryColor);
            getWindow().setStatusBarColor(translucentPrimaryColor);
        } else {

        }
    }

    /**
     * 获取虚拟导航栏的高度
     *
     * @param context
     * @return
     */
    private int getNavigationBarHeight(Context context) {
        return getSystemComponentDimen(context, "navigation_bar_height");
    }

    /**
     * 获取状态栏的高度
     *
     * @param context
     * @return
     */
    private int getStatusBarHeight(Context context) {
        return getSystemComponentDimen(context, "status_bar_height");
    }

    /**
     * 反射获取系统的属性
     *
     * @param context
     * @param dimenName
     * @return
     */
    private static int getSystemComponentDimen(Context context, String dimenName) {
        int statusHeight = -1;
        try {
            Class<?> clazz = Class.forName("com.android.internal.R$dimen");
            Object object = clazz.newInstance();
            String heightStr = clazz.getField(dimenName).get(object).toString();
            int height = Integer.parseInt(heightStr);
            //dp--->px
            statusHeight = context.getResources().getDimensionPixelSize(height);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statusHeight;
    }

    /**
     * 判断底部是否存在NavigationBar
     *
     * @return
     */
    private static boolean hasNavigationBarShow(WindowManager windowManager) {
        Display d = windowManager.getDefaultDisplay();
        //获取整个屏幕的高度
        DisplayMetrics realDisplayMetrics = new DisplayMetrics();
        d.getRealMetrics(realDisplayMetrics);
        int realHeight = realDisplayMetrics.heightPixels;
        int realWidth = realDisplayMetrics.widthPixels;
        //获取内容展示部分的高度
        DisplayMetrics displayMetrics = new DisplayMetrics();
        d.getMetrics(displayMetrics);
        int displayHeight = displayMetrics.heightPixels;
        int displayWidth = displayMetrics.widthPixels;

        return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
    }
}
  • 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
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

使用沉浸式状态栏和导航栏

public class MainActivity extends BaseTranslucentActivity{

    private Toolbar toolbar;
    private View nav;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toolbar = (Toolbar)findViewById(R.id.toolbar);
        nav = findViewById(R.id.nav);
        setOrChangeTranslucentColor(toolbar, nav, getResources().getColor(R.color.colorPrimary_pink));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Paint的细节用法

1、设置笔帽

mPaint.setStrokeCap(Paint.Cap.BUTT);//没有
mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形
mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形
  • 1
  • 2
  • 3

2、设置滤镜

1、模糊遮罩滤镜(BlurMaskFilter)
2、浮雕遮罩滤镜(EmbossMaskFilter)
  • 1
  • 2

3、设置线条汇合处

mPaint.setStrokeJoin(Paint.Join.MITER);//锐角
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆弧
mPaint.setStrokeJoin(Paint.Join.BEVEL);//直线
  • 1
  • 2
  • 3

这里写图片描述

4、文字相关

//获得字符行间距
mPaint.getFontSpacing();
//获得字符之间的间距
mPaint.getLetterSpacing();
//设置文本删除线
mPaint.setStrikeThruText(true);
//设置下划线
mPaint.setUnderlineText(true);
//设置文本大小
mPaint.setTextSize(textSize);
//设置字体类型
mPaint.setTypeface(Typeface.ITALIC);
//加载自定义字体
Typeface.create(familyName, style)
//文字倾斜,官方推荐的-0.25f是斜体
mPaint.setTextSkewX(-0.25f);
//文本对齐方式
mPaint.setTextAlign(Align.LEFT);
mPaint.setTextAlign(Align.CENTER);
mPaint.setTextAlign(Align.RIGHT);
//计算制定长度的字符串(字符长度、字符个数、真实的长度)
int breadText = mPaint.breakText(text, measureForwards, maxWidth, measuredWidth)
float[] measuredWidth = new float[1];
int breakText = mPaint.breakText(str, true, 200, measuredWidth);
Log.i("TAG", "breakText="+breakText+", str.length()="+str.length()+", measredWidth:"+measuredWidth[0]);
//获取文本的矩形区域(宽高)
mPaint.getTextBounds(text, index, count, bounds)
//获取文本的宽度(比较粗略的结果)
float measureText = mPaint.measureText(str);
//获取文本的宽度(比较精准的)
float[] measuredWidth = new float[10];
int textWidths = mPaint.getTextWidths(str, measuredWidth);
Log.i("TAG", "measureText:"+measureText+", textWidths:"+textWidths);
  • 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

5、基线相关

FontMetrics fontMetrics = mPaint.getFontMetrics();
//绘制文本在View的左上角(基线Y的计算公式)
float baselineY = top(已知) - fontMetrics.top;
//绘制文本的在View的中间(基线Y的计算公式)
float baselineY = centerY(已知)+ (fontMetrics.bottom-fontMetrics.top)/2 - fontMetrics.bottom;
//绘制文本的在View的中间(或者是这样的)
float baselineY = centerY(已知)+ (mPaint.descent()+mPaint.ascent())/2;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

6、渲染相关

LinearGradient线性渲染
RadialGradient环形渲染、可做水波纹效果,充电水波纹扩散效果、调色板
SweepGradient梯度渲染(扫描渲染)、可做微信等雷达扫描效果
ComposeShader组合渲染
  • 1
  • 2
  • 3
  • 4

7、ColorMatrix(五阶矩阵)

//色彩的平移运算(加法运算)
//色彩的缩放运算(乘法运算)

//构造方法
ColorMatrix matrix = new ColorMatrix(new float[]{});
ColorMatrix matrix = new ColorMatrix();
matrix.set(src)

//设置色彩的缩放函数
matrix.setScale(1, 1, 1.4f, 1);

//设置饱和度(1,是原来不变;0灰色;>1增加饱和度)
matrix.setSaturation(progress);

//色彩旋转函数(axis,代表绕哪一个轴旋转,0红色,1绿色,2蓝色,degrees:旋转的度数)
matrix.setRotate(0, progress);

//ColorFilter使用的子类
ColorMatrixColorFilter:色彩矩阵的颜色顾虑器。
LightingColorFilter:过滤颜色和增强色彩的方法。(光照颜色过滤器)
PorterDuffColorFilter:图形混合滤镜(图形学的一个理论飞跃)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Canvas.drawPath

将文本画在Path上,可以形成圆弧的文本

Path path = new Path();
//Path.Direction.CW,沿外环;Path.Direction.CCW,沿内环
path.addCircle(500, 500, 200, Path.Direction.CW);
mPaint.setTextSize(50);
// 绘制路径
canvas.drawPath(path, mPaint);
String text = "大家好,我是Hensen";
canvas.drawTextOnPath(text, path, 0f, 0f, mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

封装UniversalImageLoader

public class ImageManager {

    private static final int THREAD_COUNT = 2;
    private static final int PRIORITY = 2;

    private static final int MEMORY_CACHE_SIZE = 2 * 1024 * 1024;
    private static final int DISK_CACHE_SIZE = 30 * 1024 * 1024;

    private static final int CONNECT_TIME_OUT = 5 * 1000;
    private static final int READ_TIME_OUT = 30 * 1000;

    private static volatile com.hensen.shixiongsdk.ImageLoader.ImageManager ImageManager;
    private ImageLoader imageLoader = null;

    private ImageManager(Context context) {
        initDefaultConfiguration(context);
    }

    public static com.hensen.shixiongsdk.ImageLoader.ImageManager getInstance(Context context) {
        if (ImageManager == null) {
            synchronized (com.hensen.shixiongsdk.ImageLoader.ImageManager.class) {
                if (ImageManager == null) {
                    ImageManager = new ImageManager(context);
                }
            }
        }
        return ImageManager;
    }

    private void initDefaultConfiguration(Context context) {
        ImageLoaderConfiguration configuration = new ImageLoaderConfiguration
                .Builder(context)
                .threadPoolSize(THREAD_COUNT)
                .threadPriority(Thread.NORM_PRIORITY - PRIORITY)
                .denyCacheImageMultipleSizesInMemory()
                .memoryCacheSize(MEMORY_CACHE_SIZE)
                .diskCacheSize(DISK_CACHE_SIZE)
                .diskCacheFileNameGenerator(new Md5FileNameGenerator())
                .tasksProcessingOrder(QueueProcessingType.LIFO)
                .defaultDisplayImageOptions(getDefaultOptions())
                .imageDownloader(new BaseImageDownloader(context, CONNECT_TIME_OUT, READ_TIME_OUT))
                .build();
        com.nostra13.universalimageloader.core.ImageLoader.getInstance().init(configuration);
        imageLoader = com.nostra13.universalimageloader.core.ImageLoader.getInstance();
    }

    private DisplayImageOptions getDefaultOptions() {
        DisplayImageOptions defaultOptions = new DisplayImageOptions
                .Builder()
                .cacheOnDisk(true)
                .cacheInMemory(true)
                .considerExifParams(true)
                .imageScaleType(ImageScaleType.IN_SAMPLE_INT)
                .bitmapConfig(Bitmap.Config.RGB_565)
                .decodingOptions(new BitmapFactory.Options())
                .resetViewBeforeLoading(true)
                .build();
        return defaultOptions;
    }

    public void displayImage(String url, ImageView imageView,
                             DisplayImageOptions options,
                             ImageLoadingListener loadingListener) {
        imageLoader.displayImage(url, imageView, options, loadingListener);
    }

    public void displayImage(String url, ImageView imageView,
                             ImageLoadingListener loadingListener) {
        displayImage(url, imageView, null, loadingListener);
    }

    public void displayImage(String url, ImageView imageView) {
        displayImage(url, imageView, null);
    }
}
  • 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
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

启动页面优化

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //1、ViewStub优化
    viewStub = (ViewStub)findViewById(R.id.content_viewstub);
    //判断当窗体加载完毕的时候,立马再加载真正的布局进来
    getWindow().getDecorView().post(new Runnable() {
        @Override
        public void run() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    viewStub.inflate();
                }
            } );
        }
    });
    //2、判断当窗体加载完毕的时候执行,延迟一段时间执行闪屏页面动画并关闭
    getWindow().getDecorView().post(new Runnable() {
        @Override
        public void run() {
            mHandler.postDelayed(new DelayRunnable() ,2000);
        }
    });
}
  • 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

Gradle解决依赖版本冲突

如果项目中存在两个不同版本的依赖,则可以选择通过Gradle脚本进行筛选

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.android.support') {
            if (!requested.name.startsWith("multidex")) {
                details.useVersion '26.1.0'
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Gradle引入aar

和jar包不一样的是,aar是通过Gradle脚本引入的

repositories {
    flatDir {
        dir "../${project.name}/libs"
    }
}

dependencies {  
    implementation(name: 'aar名字', ext: 'aar')  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果aar包有很多,也可以统一添加一个文件夹下的所有aar

def dir = new File('app/libs')
dir.traverse(
        nameFilter: ~/.*\.aar/
) { file ->
    def name = file.getName().replace('.aar', '')
    implementation(name: name, ext: 'aar')
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果引入的aar包中有重复的资源文件时,需要在主项目的gradle移除重复的资源

packagingOptions {
    exclude 'META:-INF/LICENSE.txt'
}
  • 1
  • 2
  • 3

如果引入的aar包的清单文件和我们的app清单文件属性冲突时:用tools:replace=”属性名”解决

annotationProcessor与compileOnly的区别

annotationProcessor与compileOnly都是只编译并不打入apk中

  • annotationProcessor:编译时生成代码,编译完就不需要了
  • compileOnly:有重复的库时,可以剃除重复库,只保留一个库

为什么对象序列化要定义serialVersionUID

对于实现了java.io.Serializable接口的实体类来说,往往都会手动声明serialVersionUID,因为只要你实现了序列化,java自己就会默认给实体类加上一个serialVersionUID。java默认添加的serialVersionUID是会根据实体类的成员(成员变量,成员方法)变化而变化的。当我们把实体类序列化到本地后,如果实体类的成员发生了变化,默认添加的serialVersionUID就会发生变化。此时硬盘上序列化对象的serialVersionUID与实体类中的serialVersionUID对不上,就会反序列化失败爆出异常。所以,通常对于实现了SerialVersionUID接口的实体类来说,都会手动声明serialVersionUID

IdleHandler的使用

IdleHandler调用时机是在线程空闲的时候,可以执行回调中的方法queueIdle,一般实现线程空闲的操作

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    /**
     * @return true,表示要保留保留,代表不移除这个idleHandler,可以反复执行
     * @return false,代表执行完毕之后就移除这个idleHandler, 也就是只执行一次
     */
    @Override
    public boolean queueIdle() {

        return false;
    }
});  

猜你喜欢

转载自blog.csdn.net/oneblue123/article/details/80903721