Android开发细节——开发实战过程中遇到的细节问题与解决方案汇总

xiaoxiao2021-02-28  80

获取系统时间的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; }

2、获取12小时制的时间

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

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

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

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

解决方法

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

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

做到商品购物车模块的时候,发现价钱的加减并不能单纯的使用+、-来实现,由于我们的价格都是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(); } }

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

启动模式的细节

情景发生:当我们应用程序按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>

接着,创建该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(); } } } } }

优化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; } }

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); } }

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

在项目中,常常会因为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; } }

Context的应用场景

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

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

java.lang.IllegalArgumentException: pointerIndex out of range

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

@Override public boolean dispatchTouchEvent(MotionEvent ev) { try { return super.dispatchTouchEvent(ev); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } return false; }

使用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(); } } } } } }

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

tv_content.setText(Html.fromHtml(data.getContent(), new ImageGetterImpl(tv_content, data.getContent(), DensityUtils.getDisplayWidth(this), DensityUtils.getDisplayHeight(this)), null));

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

网络缓存

打开缓存

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); }

清除缓存

HttpResponseCache cache = HttpResponseCache.getInstalled(); if (cache != null) { cache.delete(); }

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

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

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

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

由于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) { } }

屏幕像素的适配

一般我们开发的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(); } };

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

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

安卓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>

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>

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,这个全局变量就是我们拍照后的文件路径,直接想怎么用就怎么用 //这个是高清图,不是缩略图,所以要上传服务器的时候记得压缩后上传 } }

沉浸式状态栏和导航栏

适配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; } }

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

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)); } }

Paint的细节用法

1、设置笔帽

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

2、设置滤镜

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

3、设置线条汇合处

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

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);

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;

6、渲染相关

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

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:图形混合滤镜(图形学的一个理论飞跃)

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);

封装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); } }

启动页面优化

@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); } }); }

IdleHandler的使用

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

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

ListView覆盖Item的点击事件

开发中很常见的一个问题,项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承BaseAdapter,在adapter中按照需求进行编写,问题就出现了,可能会发生点击每一个item的时候没有反应,无法获取的焦点。原因多半是由于在你自己定义的Item中存在诸如Button,CheckBox等子控件,此时这些子控件会将焦点获取到,所以常常当点击item时变化的是子控件,item本身的点击没有响应, 这时候就可以使用descendantFocusability来解决啦。该属性是当一个view获取焦点时,定义viewGroup和其子控件两者之间的关系。属性的值有三种

beforeDescendants:viewgroup会优先其子类控件而获取到焦点afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

通常我们用到的是第三种,即在Item根布局加上android:descendantFocusability="blocksDescendants"的属性

Fragment嵌套SurfaceView切换时闪黑屏

最近在开发的时候用的是ViewPager+Fragment进行底部Tab页的切换,但在最后一个Tab中Fragment嵌入了SurfaceView,用于Flutter开发,不料在切换Tab的时候SurfaceView的Tab会导致其他Tab切换过去的时候显示黑屏或者白屏等情况(黑屏、白屏主要取决你设置的Theme),解决方法是在SurfaceView上进行这两句话设置

setZOrderOnTop(true); setZOrderMediaOverlay(true); mHolder.setFormat(PixelFormat.TRANSPARENT);

java.lang.NoSuchMethodError

在我们崩溃系统中会看到这种崩溃

java.lang.NoSuchMethodError: No static method Desc(I)Ljava/lang/String; in class Lcom/yy/platform/baseservice/ConstCode$SrvResCode; or its super classes (declaration of 'com.yy.platform.baseservice.ConstCode$SrvResCode' appears in /data/app/com.yy.yinfu-0eluUMz7kO0sb_RhAZpHNw==/split_lib_dependencies_apk.apk!classes2.dex)

从崩溃的日志上翻译出来的意思是,找不到该类的方法,但是又在classes2.dex发现了这个类的方法,这是因为在多Dex的情况下,我们启动需要的class都需要放在maindex中,由于这个类放在了classes2.dex,所以报出找不到的崩溃。由于我们是Debug包,所以不加处理,但是在Release包中我们对混淆过后的这个类放在maindex中

release { ...... multiDexKeepProguard file('multidex.keep') ...... }

在multidex.keep中加入我们找不到的class即可

-keep class com.xxx.xxx.xxx$xxxx

Home键回来后会先拉起闪屏的问题

在某些机型上,第一次安装点击icon启动应用,然后home键退到后台,再次点击icon启动应用,会先拉起闪屏的问题

class SplashActivity : BaseVMActivity<SplashViewModel>() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) /** * 如果此页面不是任务栈中根activity,说明应用有其他activity存在,此时不需要拉起splash页面,任务栈被切到前台会自动显示栈顶activity */ if (!isTaskRoot) { //如果是外部跳转的Intent也会跑到这里,需要特殊处理 SchemaUtils.gotoArouter(intent) finish() return } setContentView(R.layout.activity_splash) } }

RecyclerView实现点击拖拽而不是长按拖拽

封装了一个左右拖拽的辅助类,不能左右滑动,但是点击就能左右拖动Item,如果需要实现其他需求,要自己去定制

class ListeningKonwPeopleRecyclerViewController(var context: Context, var recyclerView: RecyclerView, var data: ArrayList<LisnteningKonwPeopleInfo>) { var helper: ItemTouchHelper? = null var adapter: ListeningKonwPeopleCoverAdapter? = null var layoutManager: LinearLayoutManager? = null init { layoutManager = object : LinearLayoutManager(context, HORIZONTAL, false) { override fun canScrollHorizontally(): Boolean { return false } } adapter = ListeningKonwPeopleCoverAdapter(data) recyclerView.layoutManager = layoutManager recyclerView.adapter = adapter helper = ItemTouchHelper( object : ItemTouchHelper.Callback() { override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { val dragFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT//拖拽 return ItemTouchHelper.Callback.makeMovementFlags(dragFlags, 0) } override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { //滑动事件 Collections.swap(data, viewHolder.adapterPosition, target.adapterPosition) adapter?.notifyItemMoved(viewHolder.adapterPosition, target.adapterPosition) return false } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { //侧滑事件 } override fun isLongPressDragEnabled(): Boolean { return false } }) helper?.attachToRecyclerView(recyclerView) } fun attach() { adapter?.setOnTouchClickListener( object : ListeningKonwPeopleCoverAdapter.OnTouchClickListener { override fun onTouchClick(info: LisnteningKonwPeopleInfo, holder: ListeningKonwPeopleCoverAdapter.VH) { helper?.startDrag(holder) } }) } fun dettach() { adapter?.setOnTouchClickListener( object : ListeningKonwPeopleCoverAdapter.OnTouchClickListener { override fun onTouchClick(info: LisnteningKonwPeopleInfo, holder: ListeningKonwPeopleCoverAdapter.VH) { return } }) } }

使用

activity?.let { recyclerViewController = ListeningKonwPeopleRecyclerViewController(it, view.rv_cover, data) recyclerViewController?.attach() }

键盘弹起不会顶上去视图

键盘弹起要让视图跟着弹起解决方案太多,但是有个比较简单的方案,可以适合大多数场景,这场适合于键盘盖住视图的情况下使用,在盖住的布局中使用android:fitsSystemWindows="true"即可

<com.yy.mobile.memoryrecycle.views.YYLinearLayout android:id="@+id/input_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@color/common_color_11" android:fitsSystemWindows="true" android:gravity="center" android:minHeight="@dimen/moment_chat_input_height" android:orientation="vertical" android:visibility="gone">

为什么View.startAnimation不起作用?

看了一下View里面的源码,发现确实有一些地方判断了如果不是visible的,那么就不调用invalidate方法,也就不会去处理Animation的事情。以后startAnimation的时候,—定要选─个总是可见的View哦

错误的用法

private fun startShowAnimation(from: AnimationFrom, animationView: View, centerX: Float, centerY: Float) { val scaleAnimation = ScaleAnimation(0f, 1f, 0f, 1f, centerX, centerY) scaleAnimation.duration = 250 scaleAnimation.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationRepeat(animation: Animation?) { } override fun onAnimationEnd(animation: Animation?) { } override fun onAnimationStart(animation: Animation?) { animationView.visibility = View.VISIBLE } }) animationView.startAnimation(scaleAnimation) }

正确的用法

private fun startShowAnimation(from: AnimationFrom, animationView: View, centerX: Float, centerY: Float) { animationView.visibility = View.VISIBLE val scaleAnimation = ScaleAnimation(0f, 1f, 0f, 1f, centerX, centerY) scaleAnimation.duration = 250 scaleAnimation.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationRepeat(animation: Animation?) { } override fun onAnimationEnd(animation: Animation?) { } override fun onAnimationStart(animation: Animation?) { } }) animationView.startAnimation(scaleAnimation) }
转载请注明原文地址: https://www.6miu.com/read-39959.html

最新回复(0)