Android

android 小技巧

2021-12-13  本文已影响0人  客观开发者

1,字体随着宽度大小变化
https://github.com/ZwwwDamon/AutoScaleTextView
textview 一个不能显示全,就换另一行了
方案:

2,字体大小适配
AppCompatTextView 的使用。

 android:autoSizeMinTextSize="8dp"
    android:autoSizeTextType="uniform"
    android:autoSizeMaxTextSize="18dp"
    android:maxLines="1"

第三方me.grantland.widget.AutofitTextView

3,json 数据解析 android utils 里面的json 解析方案

 var bean: Entity<RecordeDetail> =
                GsonUtils.fromJson(
                    data,
                    字典
                    GsonUtils.getType(
                        Entity::class.java, GsonUtils.getType(RecordeDetail::class.java)
                    )
                    数组
                    GsonUtils.getType(
                        Entity::class.java, GsonUtils.getListType(RecordeDetail::class.java)
                    )
                )

4,圆角
https://github.com/RaphetS/RoundImageView
https://blog.csdn.net/ldld1717/article/details/106652831

5,blog -android
https://www.gcssloop.com/#blog
6.RecyclerView的滑动监听
滑动或者向上滑动监听
http://www.demodashi.com/demo/17113.html
https://www.jianshu.com/p/a392ab3b8aec

7.清明节 的时候app 变灰色
国家公祭日 变灰色。。
勿忘国耻!!!


//        这个是界面变成灰色处理
        var time = TimeUtils.getStringTodayMY()
//         当每年的清明节的时候,app 变成灰色
        time = time.replace(Regex("0"), "")
        if (time == "4-4") {
            val paint = Paint()
            val cm = ColorMatrix()
            cm.setSaturation(0F)
            paint.colorFilter = ColorMatrixColorFilter(cm)
            window.decorView.setLayerType(View.LAYER_TYPE_HARDWARE, paint)
        } else {
            val paint = Paint()
            window.decorView.setLayerType(View.LAYER_TYPE_HARDWARE, paint)
        }

8,BubbleView和TipView的使用,是app 更加友好

  1. 提示音
//播放系统提示音提示下载完成
                        Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
                        Ringtone r = RingtoneManager.getRingtone(application, notification);
                        r.play();

10,个别机型 界面直接跳转回会跳动,闪烁情况。
去掉动画就行了。
theme.xml中配置
<item name="android:windowAnimationStyle">@style/Animation</item>

<style name="Animation">
<item name="android:activityOpenEnterAnimation">@null</item>
<item name="android:activityOpenExitAnimation">@null</item>
<item name="android:activityCloseEnterAnimation">@null</item>
<item name="android:activityCloseExitAnimation">@null</item>
<item name="android:taskOpenEnterAnimation">@null</item>
<item name="android:taskOpenExitAnimation">@null</item>
<item name="android:taskCloseEnterAnimation">@null</item>
<item name="android:taskCloseExitAnimation">@null</item>
<item name="android:taskToFrontEnterAnimation">@null</item>
<item name="android:taskToFrontExitAnimation">@null</item>
<item name="android:taskToBackEnterAnimation">@null</item>
<item name="android:taskToBackExitAnimation">@null</item>
</style>

想要加个别的有动画,就单独页面去配置

11,蒲公英统计的坑,和版本更新。
之前用他的版本更新,用自己的,但是统计还用,但是,版本更新的方法还得留着。坑死你。

12, //导入SDK相关依赖jar、aar
implementation fileTree(include: ['.jar'], dir: 'libs')
implementation fileTree(include: ['
.aar'], dir: 'libs')

13.ndk so 文件
配置
ndk {
abiFilters 'x86','armeabi-v7a',"arm64-v8a" //不支持armeabi
}

android.useDeprecatedNdk=true

这个易忘记
14
kotlin 单例

//    private object Single {
//        var userId by SpUtils("userId", 0)
//        val sin: AppDataBase = Room.databaseBuilder(
//            BaseApp.context!!,
//            AppDataBase::class.java,
//            "material_cart$userId.db"
//        )
//            .allowMainThreadQueries()
//            .build()
//    }

...

companion object {

    @Volatile private var INSTANCE: AppDatabase? = null

    fun getInstance(context: Context): AppDatabase =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: buildDatabase(context).also {
                    INSTANCE = it
                }
            }

    private fun buildDatabase(context: Context) =

            Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "Fazendao.sqlitedb"
            )
            .addMigrations(Migration1315)
            .build()

    }
}

object 基本上就是一个单例了。,上面反而复杂了。

15 ,下拉刷新
配置

SmartRefreshLayout.setDefaultRefreshFooterCreator((context, layout) -> {
            layout.setEnableFooterFollowWhenNoMoreData(true);
            layout.setEnableFooterTranslationContent(true);
            layout.setFooterHeight(153f);
            layout.setFooterTriggerRate(0.6f);
            return new ClassicsFooter(context).setDrawableSize(20);
        });

设置更多 无数据的情况ui
16,java8 android应用

var d = list1.stream().filter { data -> data.whetherRequire == 1 }
                                .collect(Collectors.toList())
                            
 var d = list1.stream().filter(object : Predicate<ResultFileEnclosureData>{
                      override fun test(t: ResultFileEnclosureData): Boolean {
                             return data.whetherRequire == 1
//                                    return true
           }
    })
   .collect(Collectors.toList())
  1. 携程的使用。
    回到的函数,让其就行回调使用
suspend fun getLocaltion(): AMapLocation {
        return return suspendCoroutine { cancellableContinuation ->
            LocationUtils.instance().requestLocation { a ->
                cancellableContinuation.resume(a)
            }
        }
    }

suspendCoroutine 的使用。错误的使用。。。

https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine
官网对runBlocking launch suspend 一起使用,启动顺序 都很好案例。

让其 网络获取更加简单 + okhttp

  1. aroute 的使用
    极光推送demo 里面看的思想很好。。
private void initRouter() {
        if (BuildConfig.DEBUG) {
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);

        ARouter.getInstance().build(ServiceConstant.SERVICE_SHARE).navigation();
        ARouter.getInstance().build(ServiceConstant.SERVICE_LINK).navigation();
        ARouter.getInstance().build(ServiceConstant.SERVICE_PUSH).navigation();
        ARouter.getInstance().build(ServiceConstant.SERVICE_VERIFY).navigation();
        ARouter.getInstance().build(ServiceConstant.SERVICE_UNION).navigation();
    }

这样初始化很多东西了。。。都可以很好的解决问题

@Route(path = ServiceConstant.SERVICE_PUSH)
public class PushServiceImpl implements InitService {

    private static final String TAG = "PushServiceImpl";


    @Override
    public void init(Context context) {

        Log.i(TAG, "PushServiceImpl init");

        JPushInterface.setDebugMode(true);
        JPushInterface.init(context);
    }
}

主要是初始化情况。。。

19、androd studio 虚拟机创建成功
虚拟机在c 盘上。我c盘内存储存不足了。修改地方就可以了
Android Studio avd虚拟机存储位置更改
https://blog.csdn.net/dinggangchi7733/article/details/80211051
创建一个 文件夹,写一个新建一个系统变量,名称为ANDROID_SDK_HOME,值为新位置,如
将c盘的.android 全部拷贝过去就可以了。

20.俩个activity 中公用一个viewmodel
https://github.com/LucasDevelop/CustomView/blob/master/app/src/main/java/com/cj/customwidget/page/viewmodel/VM1Activity.kt
https://github.com/LucasDevelop/CustomView/blob/master/app/src/main/java/com/cj/customwidget/page/viewmodel/VM2Activity.kt

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class VMScope(val scopeName:String) {
}

import androidx.activity.ComponentActivity
import androidx.lifecycle.*

/**
 * @package    com.cj.customwidget.page.viewmodel
 * @author     luan
 * @date       2020/11/2
 * @des
 */

private val vMStores = HashMap<String, VMStore>()

fun ComponentActivity.injectViewModel() {
    //根据作用域创建商店
    this::class.java.declaredFields.forEach { field ->
        field.getAnnotation(VMScope::class.java)?.also { scope ->
            val element = scope.scopeName
            var store: VMStore
            if (vMStores.keys.contains(element)) {
                store = vMStores[element]!!
            } else {
                store = VMStore()
                vMStores[element] = store
            }
            store.register(this)
            val clazz = field.type as Class<ViewModel>
            val vm = ViewModelProvider(store, VMFactory()).get(clazz)
            field.set(this, vm)
        }
    }
}

class VMStore : ViewModelStoreOwner {

    private val bindTargets = ArrayList<LifecycleOwner>()
    private var vmStore: ViewModelStore? = null

    fun register(host: LifecycleOwner) {
        if (!bindTargets.contains(host)) {
            bindTargets.add(host)
            host.lifecycle.addObserver(object : LifecycleEventObserver {
                override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        host.lifecycle.removeObserver(this)
                        bindTargets.remove(host)
                        if (bindTargets.isEmpty()) {//如果当前商店没有关联对象,则释放资源
                            vMStores.entries.find { it.value == this@VMStore }?.also {
                                vmStore?.clear()
                                vMStores.remove(it.key)
                            }
                        }
                    }
                }
            })
        }
    }

    override fun getViewModelStore(): ViewModelStore {
        if (vmStore == null)
            vmStore = ViewModelStore()
        return vmStore!!
    }
}

class VMFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return modelClass.newInstance()
    }

}

https://www.jianshu.com/p/f211ca175a25
21、viewpager2
感觉和recyclerview 一样。
就是item view 就必须高度是全屏。
默认是横向的。竖向

viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
recyclerview 也可以和viewpager 一样。修改LayoutManager 就可以。

package com.example.viewpager2.recyclerview;

import android.content.Context;
import android.util.Log;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.PagerSnapHelper;
import androidx.recyclerview.widget.RecyclerView;

public class MyLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener {

    //根据这个参数来判断当前是上滑  还是下滑
    private int mDrift;
    //传进来的监听接口类
    private OnViewPagerListener onViewPagerListener;
    //解决吸顶或者洗低的对象
    private PagerSnapHelper pagerSnapHelper;


    public MyLayoutManager(Context context) {
        super(context);
    }

    public MyLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        pagerSnapHelper = new PagerSnapHelper();
    }


    /**
     * 当MyLayoutManager完全放入到RecyclerView中的时候会被调用
     */
    @Override
    public void onAttachedToWindow(RecyclerView view) {
        view.addOnChildAttachStateChangeListener(this);
        pagerSnapHelper.attachToRecyclerView(view);
        super.onAttachedToWindow(view);
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        mDrift = dy;
        return super.scrollVerticallyBy(dy, recycler, state);
    }

    @Override
    public boolean canScrollVertically() {
        return true;
    }

    /**
     * 将Item添加进来的时候  调用这个方法
     */
    @Override
    public void onChildViewAttachedToWindow(@NonNull View view) {
        if (mDrift > 0) {
            //向上滑
            if (onViewPagerListener != null) {
                //如果是向上滑动的时候  就选中当前itemView下一个item
                onViewPagerListener.onPageSelected(view);
            }
        } else {
            //向下滑
            if (onViewPagerListener != null) {
                //如果是向上滑动的时候  就选中当前itemView下一个item
                onViewPagerListener.onPageSelected(view);
            }
        }
    }

    /**
     * 监听滑动的状态
     */
    @Override
    public void onScrollStateChanged(int state) {
        switch (state) {
            case RecyclerView.SCROLL_STATE_IDLE:
                //现在拿到的就是当前显示的这个item
                View snapView = pagerSnapHelper.findSnapView(this);
                assert snapView != null;
                if (onViewPagerListener != null) {
                    onViewPagerListener.onPageSelected(snapView);
                }
                break;
        }
        super.onScrollStateChanged(state);
    }

    /**
     * 将Item移除出去的时候  调用这个方法
     */
    @Override
    public void onChildViewDetachedFromWindow(@NonNull View view) {
        Log.e("EEEEEEEEE", "22222222222222222");
        if (mDrift >= 0) {
            //向上滑
            if (onViewPagerListener != null) {
                onViewPagerListener.onPageRelease(view);
            }
        } else {
            //向下滑
            if (onViewPagerListener != null) {
                onViewPagerListener.onPageRelease(view);
            }
        }
    }


    public void setOnViewPagerListener(OnViewPagerListener onViewPagerListener) {
        this.onViewPagerListener = onViewPagerListener;
    }
}
import android.view.View;

public interface OnViewPagerListener {
    //停止播放的监听方法
    void onPageRelease(View itemView);

    //播放的监听方法
    void onPageSelected(View itemView);
}

22、FFmpeg 命令
https://blog.csdn.net/wenmingzheng/article/details/88373192
-to 截到视频的哪个时间点结束。00:00:15是到视频的第15s结束。

如果用-t 表示截取多长的时间如 上文-to 换位-t则是截取从视频的第10s开始,截取15s时长的视频。即截出来的视频共15s.

注意的地方是:

如果将-ss放在“-i 源文件名”后面则-to的作用就没了,跟-t一样的效果了,变成了截取多长视频。一定要注意-ss的位置。

参数解析

-vcodec copy表示使用跟原视频一样的视频编解码器。

-acodec copy表示使用跟原视频一样的音频编解码器。

-i 表示源视频文件

-y 表示如果输出文件已存在则覆盖。

23、kotlin 里面的深拷贝

24、viewpager\RecyclerView 滑动边缘阴影效果
去除 viewpager 滑动到尽头时的阴影效果
android:overScrollMode=“never”
去除 RecyclerView 滑动到尽头时的阴影效果
android:overScrollMode="never"
android:scrollbars="none"
在ViewPage2中设置android:overScrollMode="never"是没有用的,阴影动画依然存在,我们要在代码中这么设置

View child = viewPager2.getChildAt(0);
if (child instanceof RecyclerView) {
child.setOverScrollMode(View.OVER_SCROLL_NEVER);
}
25、ProgressBar 个别机型 不动情况
添加这个

  android:indeterminateDrawable="@drawable/anim_drawable_bg" 
bg 内容
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/loading" 图片 静态就行
    android:fromDegrees="0.0"
    android:pivotX="50.0%"
    android:pivotY="50.0%"
    android:toDegrees="360.0" />

26、Android字体加粗,UI小姐姐说太粗了,解决办法
tv.getPaint().setStyle(Paint.Style.FILL_AND_STROKRE)
tv.getPaint().setStrokeWidth(0.7)
另外textStype="bold" 太粗

27、TextView 和特殊符号在一起 显示不全等问题

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * ClassName:TypesetTextView
 * Date:     2014-11-29 上午10:10:31
 * @author Lenovo
 * @version
 * @since JDK 1.7
 * @see
 */
public class TypesetTextView extends TextView {


    private int mLineY;

    private int mViewWidth;

    public static final String TWO_CHINESE_BLANK = "  ";


    private StringBuffer mText;
    private StringBuffer newText = null;
    private Paint mPaint;
    /**VIEW的高度*/
    private int mHeight = 0;
    /**行高*/
    private static final int LINE_HEIGHT = 40;
    private int oneLine;//一行显示文字个数

    private int number_of_words;//显示的字数

    public TypesetTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {


        String text = getText().toString();// 获取文本内容
        if (null == mText) {//以单例模式对文字进行拆分
            mText = new StringBuffer(text);
            TextPaint paint = getPaint();//获取画笔
            paint.setColor(getCurrentTextColor());// 获取文字颜色将其设置到画笔上
            paint.setTextSize(getTextSize());//设置文字大小
            paint.setTypeface(getTypeface());//设置字体,包括字体的类型,粗细,还有倾斜、颜色等
            paint.drawableState = getDrawableState();
            mViewWidth = getMeasuredWidth();//获取填写字数的宽
            mPaint = paint;
            caculateChangeLine();//对文字进行分行处理
        }
        mLineY = getPaddingTop();//设置头部内边距
        mLineY += getTextSize();
        Layout layout = getLayout();//避免出现空视图

        if (layout == null) {
            return;
        }
        Paint.FontMetrics fm = mPaint.getFontMetrics();

        int textHeight = (int) (Math.ceil(fm.descent - fm.ascent));
        textHeight = (int) (textHeight * layout.getSpacingMultiplier() + layout
                .getSpacingAdd());//获取文字的高度

        String[] split = newText.toString().split("\n");//将分割好滴文字进行排版
        if (null != split && split.length > 0) {//此处设置文本显示的高度,适配一些手机无法显示
            int i = (split.length) + 0;//多设置了几行以避免显示不全(看情况进行修改)
            int setheight = textHeight * i;
            setHeight(setheight);//设置textview高度
        }

        for (int i = 0; i < split.length; i++) {
            //此处为源例子上的写法,标点符号换行问题还是存在(楼主引用,ToDBC(aaa)的方法进行了修改,已解决这个bug)
//                layout.getLineCount()//获取显示的行数
//                int lineStart = layout.getLineStart(i);
//                int lineEnd = layout.getLineEnd(i);//获取每行要显示的字数
            String string = split[i];
            float width = StaticLayout.getDesiredWidth(string, 0,
                    string.length(), getPaint());

            if (null == string || TextUtils.isEmpty(string)) {
                continue;
            }
            int strWidth = (int) mPaint.measureText(string + "好好");//验证是否足够一个屏幕的宽度
            if (needScale(string) && string.trim().length() > number_of_words - 5 && mViewWidth < strWidth)//判断是否足够一行显示的字数,足够久进行字的处理不够则直接画出来
            //,避免出现字数不够,字间距被画出来的字间距过大影响排版
            {// 判断是否结尾处需要换行,并且不是文本最后一行
                drawScaledText(canvas, getPaddingLeft(), split[i], width, i);
            } else {

                canvas.drawText(split[i], getPaddingLeft(), mLineY, mPaint);// 将字符串直接画到控件上
            }
            mLineY += textHeight;
        }


    }

    /**
     * @Description:计算出一行显示的文字
     */
    private String caculateOneLine(String str) {
        //对一段没有\n的文字进行换行
        String returnStr = "";
        int strWidth = (int) mPaint.measureText(str);
        int len = str.length();
        int lineNum = strWidth / mViewWidth; //大概知道分多少行
        int tempWidth = 0;
        String lineStr;
        int returnInt = 0;
        if (lineNum == 0) {
            returnStr = str;
            mHeight += LINE_HEIGHT;
            return returnStr;
        } else {

            oneLine = len / (lineNum + 1);    //一行大概有多少个字
            if (number_of_words < oneLine) {
                number_of_words = oneLine;
            }

            lineStr = str.substring(0, oneLine);
            tempWidth = (int) mPaint.measureText(lineStr);


            if (tempWidth < mViewWidth) //如果小了 找到大的那个
            {
                while (tempWidth < mViewWidth) {
                    oneLine++;
                    lineStr = str.substring(0, oneLine);
                    tempWidth = (int) mPaint.measureText(lineStr);
                }
                returnInt = oneLine - 1;
                returnStr = lineStr.substring(0, lineStr.length() - 2);
            } else//大于宽找到小的
            {
                while (tempWidth > mViewWidth) {
                    oneLine--;
                    lineStr = str.substring(0, oneLine);
                    tempWidth = (int) mPaint.measureText(lineStr);
                }
                returnStr = lineStr.substring(0, lineStr.length() - 1);
                returnInt = oneLine;
            }
            mHeight += LINE_HEIGHT;
            returnStr += "\n" + caculateOneLine(str.substring(returnInt - 1));
        }
        return returnStr;
    }

    public void caculateChangeLine() {
        newText = new StringBuffer();
        String tempStr[] = mText.toString().split("\n");
        int len = tempStr.length;
        for (int i = 0; i < len; i++) {
            String caculateOneLine = caculateOneLine(tempStr[i]);
            if (!TextUtils.isEmpty(caculateOneLine)) {
                newText.append(caculateOneLine);
                newText.append("\n");
            }

        }
        this.setHeight(mHeight);
    }


    private void drawScaledText(Canvas canvas, int lineStart, String line,
                                float lineWidth, int currentline) {
        float x = 0;
        if (isFirstLineOfParagraph(lineStart, line)) {// 判断是否是第一行
            canvas.drawText(TWO_CHINESE_BLANK, x, mLineY, getPaint());
            float bw = StaticLayout.getDesiredWidth(TWO_CHINESE_BLANK, getPaint());
            x += bw;

            line = line.substring(3);
        }
        int gapCount = line.length() - 1;
        int i = 0;
        if (line.length() > 2 && line.charAt(0) == 12288
                && line.charAt(1) == 12288) {
            String substring = line.substring(0, 2);
            float cw = StaticLayout.getDesiredWidth(substring, getPaint());
            canvas.drawText(substring, x, mLineY, getPaint());
            x += cw;
            i += 2;
        }

        float d = (mViewWidth - lineWidth) / gapCount;
        for (; i < line.length(); i++) {
            String c = String.valueOf(line.charAt(i));
            float cw = StaticLayout.getDesiredWidth(c, getPaint());
            canvas.drawText(c, x, mLineY, getPaint());
            x += cw + d;
        }
    }

    private boolean isFirstLineOfParagraph(int lineStart, String line) {
        return line.length() > 3 && line.charAt(0) == ' '
                && line.charAt(1) == ' ';
    }

    private boolean needScale(String line) {// 判断是否需要换行
        if (line == null || line.length() == 0) {
            return false;
        } else {
            char charAt = line.charAt(line.length() - 1);
            return charAt != '\n';
        }
    }
}

上一篇下一篇

猜你喜欢

热点阅读