Android开发小技巧

1.Touch处理

MotionEventCompat.getActionMasked(ev)
等价于
event.getAction() & ACTION_MASK

    /**
     * Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK}
     * portion.
     */
    public static int getActionMasked(MotionEvent event) {
        return event.getAction() & ACTION_MASK;
    }

2.枚举

enum

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY 
}
public enum Day {
    MONDAY(1, "星期一", "星期一各种不在状态"),
    TUESDAY(2, "星期二", "星期二依旧犯困"),
    WEDNESDAY(3, "星期三", "星期三感觉半周终于过去了"),
    THURSDAY(4, "星期四", "星期四期待这星期五"),
    FRIDAY(5, "星期五", "星期五感觉还不错"),
    SATURDAY(6, "星期六", "星期六感觉非常好"),
    SUNDAY(7, "星期日", "星期日感觉周末还没过够。。。");

    Day(int index, String name, String value) {
        this.index = index;
        this.name = name;
        this.value = value;
    }

    private int index;
    private String name;
    private String value;

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

java enum枚举类的用法以及高级玩法


ENUM中的每一个值都是一个Object,它的每个声明都会占用运行时的部分内存以便能够引用到这个Object。因此ENUM的值会比对应的Integer和String所占用的内存多。过度在Android开发中使用ENUM将会增大DEX大小,并会增大运行时的内存分配大小。

Android中的注解方式

添加支持注解的依赖到你的项目中,需要在build.gradle文件中的依赖块中添加:
dependencies { compile 'com.android.support:support-annotations:24.2.0' }

IntDefStringDef是两个神奇的注解常量,可以用来替代Enum的使用。这些注解能够帮助我们在编译时对变量赋值进行检查。


 public static final int SUNDAY= 0;
 public static final int MONDAY= 1;
 public static final int TUESDAY= 2;
 public static final int WEDNESDAY= 3;
     ...

@IntDef({SUNDAY, MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY})
@Retention(RetentionPolicy.SOURCE)
public @interface Day{}

// 使用
@Day int today;
// Constants
public static final String WINTER = "Winter";
public static final String SPRING = "Spring";
public static final String SUMMER = "Summer";
public static final String FALL = "Fall";

// Declare the @ StringDef for these constants:
@ StringDef ({WINTER, SPRING, SUMMER, FALL})
@Retention(RetentionPolicy.SOURCE)
public @interface Season {}


// 在使用的时候,例如我们有一个变量名称为:
int color;
// 与此同时有一个函数:
void setColor(@Color int COLOR){
color = COLOR;
}
//在调用此函数的时候,参数名称如果不是IntDef中的变量名称的时候,例如
//setColor(2),Android Studio中就会提示错误(虽然编译仍然会通过)。

Android 性能:避免在Android上使用ENUM
Android开发中用于替代Enum的@IntDef的使用

3.getResources().getDrawable() 过时的解决方法

  1. 当你这个Drawable受当前Activity主题的影响时
ContextCompat.getDrawable(getActivity(), R.drawable.name);
  1. 当你这个Drawable不受主题影响时
ResourcesCompat.getDrawable(getResources(), R.drawable.name, null);
  1. 当你这个Drawable想使用另外一个主题样式时
ResourcesCompat.getDrawable(getResources(), R.drawable.name, anotherTheme);

4.填加分割线


5.获取屏幕宽高

1. 通过WindowManager获取
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
heigth = dm.heightPixels;
width = dm.widthPixels;

2. 通过Resources获取
DisplayMetrics dm = getResources().getDisplayMetrics();
heigth = dm.heightPixels;
width = dm.widthPixels;

3. 获取屏幕的默认分辨率
Display display = getWindowManager().getDefaultDisplay();
heigth = display.getWidth();
width = display.getHeight();

1、3都是使用getWindowManager()得到的,但这个是建立在类Activity上的,如果自己的类没有继承这个,则取不到数据,故个人认为通过Resources获取最好。

在非Activity类中,拿到WindowManager

((WindowManager) MyApplicationContext.context.getSystemService(Context.WINDOW_SERVICE))
  .getDefaultDisplay()
  .getMetrics(dm);

6.分享(不调用第三方SDK)

Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, "来自WanAndroid" + detailLink);
startActivity(Intent.createChooser(shareIntent, "分享"));

7.RecyclerView点击条目自动滑动到中央

public class CenterLayoutManager extends LinearLayoutManager {

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

    public CenterLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public CenterLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        RecyclerView.SmoothScroller smoothScroller = new CenterSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private static class CenterSmoothScroller extends LinearSmoothScroller {

        CenterSmoothScroller(Context context) {
            super(context);
        }

        @Override
        public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
            return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
        }
    }
}

其中calculateDtToFit的参数,viewStart viewEnd表示被点击的条目需要移动的Start/End,viewEnd - viewStart = View的宽度;boxStart boxEnd表示当前RecyclerView的Start/End,boxEnd - boxStart = RecyclerView的宽度(无Padding)。

public int calculateDyToMakeVisible(View view, int snapPreference) {
    final RecyclerView.LayoutManager layoutManager = getLayoutManager();
    if (layoutManager == null || !layoutManager.canScrollVertically()) {
        return 0;
    }
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                view.getLayoutParams();
    final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
    final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
    final int start = layoutManager.getPaddingTop();
    final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
        return calculateDtToFit(top, bottom, start, end, snapPreference);
}

8.自定义View的动画还可以这么搞

        mTabSelector = new Runnable() {
            public void run() {
                final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
                smoothScrollTo(scrollPos, 0);
                mTabSelector = null;
            }
        };
        post(mTabSelector);

9.RecyclerView滚动到当前界面第一个

scrollToPosition 会把不在屏幕的 Item 移动到屏幕上,原来在上方的 Item 移动到 可见 Item 的第一项,在下方的移动到屏幕可见 Item 的最后一项。已经显示的 Item 不会移动。

scrollToPositionWithOffset会把 Item 移动到可见 Item 的第一项,即使它已经在可见 Item 之中。另外它还有 offset 参数,表示 Item 移动到第一项后跟 RecyclerView 上边界或下边界之间的距离(默认是 0)

10.MAC快捷键

大小写转换 Cmd + Shift + U Ctrl + Shift + U
注释代码(//) Cmd + / Ctrl + /
注释代码(/**/) Cmd + Option + / Ctrl + Alt + /
格式化代码 Cmd + Option + L Ctrl + Alt + L
清除无效包引用 Option + Control + O Alt + Ctrl + O
查找 Cmd + F Ctrl + F
查找+替换 Cmd + R Ctrl + R
上下移动代码 Option + Shift + Up/Down Alt + Shift + Up/Down
删除行 Cmd + Delete Ctrl + Y
扩大缩小选中范围 Option + Up/Down Ctrl + W/Ctrl + Shift + W
快捷生成结构体 Cmd + Option + T Ctrl + Alt + T
快捷覆写方法 Ctrl + O Ctrl + O
快捷定位到行首/尾 Cmd + Left/Right Ctrl + Left/Right
折叠展开代码块 Cmd + Plus,Minus Ctrl + Plus/Minus
折叠展开全部代码块 Cmd + Shift + Plus,Minus Ctrl + Shift + Plus,Minus
文件方法结构 Cmd + F12 Ctrl + F12
查找调用的位置 Ctrl + Option + H Ctrl + Alt + H
大小写转换 Cmd + Shift + U Ctrl + Shift + U

11.CoordinatorLayout相关注意事项

  1. CoordinatorLayout继承自viewgroup,但是使用类似于framLayout,有层次结构,后面的布局会覆盖在前面的布局之上,但跟behavior属性也有很大关系,的app:layout_behavior属性,只有CoordinatorLayout的直接子布局才能响应,所以不要做徒劳无功的事

  2. CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout结合起来才能产生这么神奇的效果,不要想当然的光拿着CoordinatorLayout就要玩出来(刚接触的时候我也有这种想法),累死你

  3. AppBarLayout:继承自lineLayout,使用时其他属性随lineLayou,已经响应了CoordinatorLayout的layout_behavior属性,所以他能搞出那么多特效来

  4. AppBarLayout的直接子控件可以设置的属性:layout_scrollFlags

1.scroll|exitUntilCollapsed如果AppBarLayout的直接子控件设置该属性,该子控件可以滚动,向上滚动NestedScrollView出父布局(一般为CoordinatorLayout)时,会折叠到顶端,向下滚动时NestedScrollView必须滚动到最上面的时候才能拉出该布局
2.scroll|enterAlways:只要向下滚动该布局就会显示出来,只要向上滑动该布局就会向上收缩
3.scroll|enterAlwaysCollapsed:向下滚动NestedScrollView到最底端时该布局才会显示出来
4.如果不设置改属性,则改布局不能滑动

  1. CollapsingToolbarLayout,字面意思是折叠的toolbar,它确实是起到折叠作用的,可以把自己的自布局折叠 继承自framLayout,所以它的直接子类可以设置layout_gravity来控制显示的位置,它的直接子布局可以使用的属性:app:layout_collapseMode(折叠模式):可取的值如下:

1.pin:在滑动过程中,此自布局会固定在它所在的位置不动,直到CollapsingToolbarLayout全部折叠或者全部展开
2.parallax:视察效果,在滑动过程中,不管上滑还是下滑都会有视察效果,不知道什么事视察效果自己看gif图(layout_collapseParallaxMultiplier视差因子 0~1之间取值,当设置了parallax时可以配合这个属性使用,调节自己想要的视差效果)
3.不设置:跟随NestedScrollView的滑动一起滑动,NestedScrollView滑动多少距离他就会跟着走多少距离

  1. toobar最好是放在CollapsingToolbarLayout,也不是没有其他用法,但是在这套系统中一般只能放在CollapsingToolbarLayout里面,才能达到好的效果,这里toolbar同时设置layout_gravity和app:layout_collapseMode时有一些比较复杂的情况.不一一作介绍,因为一般我们只会把toolbar放在最上面(不用设置layout_gravity属性,默认的),并且设置app:layout_collapseMode为pin,让他固定在最顶端,有兴趣的自己试试其他情况,

  2. 告你一个惊天大幂幂:只要CollapsingToolbarLayout里面包含有toolbar那么CollapsingToolbarLayout的折叠后高度就是toolbar的高度,相当于CollapsingToolbarLayout设置了minHeight属性,

  3. 再告诉你一个惊天大咪咪:CollapsingToolbarLayout折叠到最顶端时,它就是老大,会处于最上层,包括toolbar在内,所有的布局都会被他盖住,显示不出来.

  4. CollapsingToolbarLayout 自己的属性 说明,不是直接子布局可用的,是自己可以用的属性
    contentScrim折叠后的颜色也是展开时的渐变颜色,效果超赞.
    title标题,如果设置在折叠时会动画的显示到折叠后的部分上面,拉开时会展开,很好玩的
    expandedTitleMargin当title文字展开时文字的margin,当然还有marginTop等属性,脑补吧
    app:collapsedTitleTextAppearance=”@style/Text”折叠时title的样式里面可以定义字体大小颜色等
    app:collapsedTitleTextAppearance=”@style/Text1”折叠时title的样式里面可以定义字体大小颜色等
    当然还有一些,自己试试吧,现在的这些已经完全够用了

  5. 还有最后一个问题:怎么实现固定表头,这个也简单,写一个布局放在CollapsingToolbarLayout之后,AppBarLayout之内即可,xml文件中自己找找看吧.你要是问如果放在CollapsingToolbarLayout之前,AppBarLayout之内会怎么样?这样折叠布局就不起作用了.不会折叠了.
    https://blog.csdn.net/qq_31340657/article/details/51918773

12. startActivities

    fun multiIntent(view : View) {
        val intent_1   = Intent(this,IntentActivityA::class.java)
        val intent_2   = Intent(this,IntentActivityB::class.java)
        val intent_3   = Intent(this,IntentActivityC::class.java)
        
        startActivities(arrayOf(intent_1,intent_2,intent_3))
    }
image
 IntentActivity C onCreate
 IntentActivity B onCreate
 IntentActivity C onDestroy
 IntentActivity A onCreate
 IntentActivity B onDestroy
 IntentActivity A onDestroy

注意不是全部创建压入栈里的。相邻原则!!!

13.代码中更改margin

FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) tv.getLayoutParams();
layoutParams.setMargins(0, 0, DisplayUtils.dip2px(Main2Activity.this,12), DisplayUtils.dip2px(Main2Activity.this,12));
tv.setLayoutParams(layoutParams);

注意:getLayoutParams()获取到的是tv的父布局的类型!!!!!如果它的父布局是FrameLayout而你转成LinearLayout.LayoutParams。程序会报错!

14.Gson中的TypeToken使用

List result = new Gson().fromJson(cacheContent, new TypeToken>() { }.getType());

使用它可以将String转换成List数组结构。

15. SparseArray和SparseArrayCompat

SparseArray以键值对的形式保存数据,key是int类型,并且是唯一的不允许重复的key,而value可以是任何object。

优点
相比HashMap更加节省内存空间,数据存储只依赖key和value2个数组,数组空间是可复用的,数据的存储密度较高。
因为key数组是有序的,通过key获取value相对高效。

缺点:
key数组是保持有序的,在插入和查找时,通过二分法确定位置key的下标,比较耗时;
插入删除等操作可能会移动数组数据。

综合来说,SparseArray适用于小数据量的键值对场景。数据量达到几百时,效率优势和HashMap相比已不明显。

SparseArray只能在API19以上的系统里面 才有这个类,也就是Android4.4以上。所以Android为我们提供了一个兼容的类SparseArrayCompat,使用这个可以兼容更低的版本。

    private static final int BASE_ITEM_TYPE_HEADER = 100000;
    
    private SparseArrayCompat mHeaderViews = new SparseArrayCompat<>();

    public void addHeaderView(View view) {
        mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderViewPos(position)) {
            return mHeaderViews.keyAt(position);
        } else if (isFooterViewPos(position)) {
            return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
        }
        return mInnerAdapter.getItemViewType(position - getHeadersCount());
    }

16.隐藏状态栏

 

设置全屏会自动隐藏状态栏!!!但是不会隐藏标题栏,所以想要全屏需要windowNoTitle、windowFullscreen一起设置!

17.List中的T可以为接口

效果同List<?extends MyInterface>

18.从View中获取Context的正确姿势

在继承自AppCompatActivity时,Android底层会将我们应用的控件转为v7包中对应的控件,Context就被替换成了TintContextWrapper(它继承自ContextWrapper)。
或者
View 可能继承自 AppCompat 系的 View (比如 AppCompatTextView, AppCompatImageView),此时的context存储于TintContextWrapper的getBaseContext中,所以需要以下处理:

        Context context = v.getContext();
        if (context instanceof TintContextWrapper) {
            context = ((TintContextWrapper) context).getBaseContext();
        }
        if (context instanceof Activity) {
            ((Activity) context).finish();
        }

或者

   public static Activity getActivityFromView(View view) {
        Context context = view.getContext();
        while (context instanceof ContextWrapper) {
            if (context instanceof Activity) {
                return (Activity) context;
            }
            context = ((ContextWrapper) context).getBaseContext();
        }
        return null;
    }

Android 从 View 中获取 Activity 时遇到 TintContextWrapper cannot be cast to 的问题

19.文件读写操作时,先进行判断

   public static String getCachePath(Context context) {
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())) {
            // 有sd卡
            try {
                return context.getExternalCacheDir().getAbsolutePath();
            } catch (Exception e) {
                e.printStackTrace();
                return context.getCacheDir().getAbsolutePath();
            }
        } else {
            // 无sd卡
            return context.getCacheDir().getAbsolutePath();
        }
    }

20.点击通知的跳转逻辑

startActivities(new Intent[]{mainIntent, targetIntent});

这样的话,先跳转目标页,点击返回跳转到主页面。

21.RecyclerView的小技巧

使内容等间距排列,spanCount是个数,spacing是两个Item的间距。

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view); // item position
        int column = position % spanCount; // item column
        outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
        outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
        if (position >= spanCount) {
            outRect.top = spacing; // item top
        }
    }

22.判断intent是否能跳转/是否有效

    public static boolean checkIntent(Intent intent) {
        return checkIntent(intent, PackageManager.GET_INTENT_FILTERS);
    }


    public static boolean checkIntent(Intent intent, int flags) {
        PackageManager pm = RootInit.getInstance().getPackageManager();
        List activities = pm.queryIntentActivities(intent, flags);
        final int size = (activities == null) ? 0 : activities.size();
        return size > 0;
    }

23.WebView配置

private void initWebViewSetting() {
        WebSettings ws = getSettings();
        try {
            ws.setJavaScriptEnabled(true);
            // Logger
        } catch (Exception e) {
            e.printStackTrace();
        }
        ws.setSupportZoom(false);
        String appCacheDir = Paths.getInternalCachePath(getContext());
        ws.setDomStorageEnabled(true);
        ws.setAppCacheMaxSize(1024 * 1024 * 4);//设置缓冲
        ws.setAppCachePath(appCacheDir);
        ws.setAllowFileAccess(true);
        ws.setAppCacheEnabled(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ws.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
        ws.setJavaScriptCanOpenWindowsAutomatically(true);

        ws.setDefaultTextEncodingName("utf-8");
        ws.setUseWideViewPort(true);
        ws.setLoadWithOverviewMode(true);
        ws.setBuiltInZoomControls(false);
        // webView.setBackgroundColor(0);
        ws.setCacheMode(WebSettings.LOAD_NO_CACHE);
        removeJavascriptInterface("searchBoxJavaBridge_");
        removeJavascriptInterface("accessibility");
        removeJavascriptInterface("accessibilityTraversal");
        ws.setAllowFileAccess(true);// 设置允许访问文件数据
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            ws.setAllowUniversalAccessFromFileURLs(false);
            ws.setAllowFileAccessFromFileURLs(false);
        }
        setWebChromeClient(new BpWebChromeClient());
        setWebViewClient(new BpWebViewClient());
        setDownloadListener(new DownloadListener() {
            @Override
            public void onDownloadStart(String url, String userAgent, String contentDisposition,
                                        String mimetype, long contentLength) {
                if (null != url) {
                    Uri uri = Uri.parse(url);
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    if (ToolBox.checkIntent(intent)) {
                        try {
                            getActivity().startActivity(intent);
                        } catch (Throwable e) {
                            YLog.e(e);
                        }
                    }
                }
            }
        });
    }

24. PopupWindow 沉浸式

pop.setClippingEnabled(false)

25. Dialog 沉浸式

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
    dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
    dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}

26.android studio预览可视化

 if (thisView.isInEditMode()) {//这段代码在运行时不会执行,只会在Studio编辑预览时运行,不用在意性能问题
            final int d = SmartUtil.dp2px(5);
            final Context context = thisView.getContext();

            Paint paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(0xcccccccc);
            paint.setStrokeWidth(SmartUtil.dp2px(1));
            paint.setPathEffect(new DashPathEffect(new float[]{d, d, d, d}, 1));
            canvas.drawRect(d, d, thisView.getWidth() - d, thisView.getBottom() - d, paint);

            TextView textView = new TextView(context);
            textView.setText(context.getString(R.string.srl_component_falsify, getClass().getSimpleName(), SmartUtil.px2dp(thisView.getHeight())));
            textView.setTextColor(0xcccccccc);
            textView.setGravity(Gravity.CENTER);
            //noinspection UnnecessaryLocalVariable
            View view = textView;
            view.measure(makeMeasureSpec(thisView.getWidth(), EXACTLY), makeMeasureSpec(thisView.getHeight(), EXACTLY));
            view.layout(0, 0, thisView.getWidth(), thisView.getHeight());
            view.draw(canvas);
        }

27.descendantFocusability子布局获取焦点

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

注意:它只在RelativeLayout中设置生效!!!

28.注解生命周期

  1. RetentionPolicy.SOURCE源码注解,编译成.class文件后注解就不存在,用来提示开发者
  2. RetentionPolicy.CLASS CLASS汇编注解,编译成.class文件后注解也还存在,用于自动生成代码
  3. RetentionPolicy.RUNTIME 运行时动态注解,生命周期一直程序运行时都存在,常用于自动注入

29.绘制完加载布局

        final View layout = findViewById(Window.ID_ANDROID_CONTENT);
        ViewTreeObserver vto = layout.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                } else {
                    layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
                initUIandEvent();
            }
        });

30.光晕效果(蒙层)

BlurMaskFilter

/**
 * This takes a mask, and blurs its edge by the specified radius. Whether or
 * or not to include the original mask, and whether the blur goes outside,
 * inside, or straddles, the original mask's border, is controlled by the
 * Blur enum.
 */
 /* 翻译成大白话的意思就是BlurMaskFilter可以在原本的View上添加一层指定模糊半径的蒙层,具体模糊的方式,由Blur枚举类型控制 */
// 光晕的paint
private val outPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        // 光晕的颜色
        color = Color.parseColor("#e6e8db")
        // 使用BlurMaskFilter制作阴影效果
        maskFilter = BlurMaskFilter(shadowRadius.toFloat(), BlurMaskFilter.Blur.SOLID)
    }

31.RecyclerView融边效果

android:fadingEdgeLength="30dp"
android:requiresFadingEdge="horizontal"

32.DialogFragment全屏显示

        final Window window = getDialog().getWindow();
        window.setBackgroundDrawableResource(R.color.transparent);
        window.getDecorView().setPadding(0, 0, 0, 0);
        WindowManager.LayoutParams lp = window.getAttributes();
        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;

注意:第二行和第三行必须设置才能全屏,亲测!

33. 自定义控件调整位置

offsetTopAndBottom、offsetLeftAndRight

43.键盘弹出背景不动

手机屏幕的高为1920px,那么整个Activity的布局高度也为1920px。当设置该属性后点击界面中的EditText,此时弹出软键盘其高度为800px。为了完整地显示此软键盘,系统会调整Activity布局的高度为1920px-800px=1120px。

当 windowSoftInputMode 被设置为 adjustResize 时候,当布局调整的时候被调整的布局均会重绘制,并走了onMeasure,onSizeChanged,onLayout 。
当 windowSoftInputMode 被设置为 adjustPan 时候,当布局调整的时候被调整的布局均会重绘制,并走了onMeasure, onLayout 。

这里只需要注意 两者都走了 onMeasure 方法,至于 adjustPan 没走 onSizeChanged。

所以只需重写onMeasure方法,即可实现。

44.PopupWindow透传致其它View响应点击事件

解决:只需要让PopupWindow持有焦点即可。

this.setFocusable(true);

//最后一个参数为true
new PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);

45. dialog相关背景设置

1.设置透明度(Dialog自身的透明度)

WindowManager.LayoutParams lp=dialog.getWindow().getAttributes();
lp.alpha=1.0f;
dialog.getWindow().setAttributes(lp);

alpha在0.0f到1.0f之间。1.0完全不透明,0.0f完全透明

2.设置黑暗度(Dialog自身的黑暗度)

dialog.setContentView(R.layout.dialog);
WindowManager.LayoutParams lp=dialog.getWindow().getAttributes();
lp.dimAmount=1.0f;
dialog.getWindow().setAttributes(lp);
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

dimAmount在0.0f和1.0f之间,0.0f完全不暗,1.0f全暗

3.设置Dialog底背景模糊和黑暗度

WindowManager.LayoutParams.FLAG_BLUR_BEHIND(设置模糊)

WindowManager.LayoutParams.FLAG_DIM_BEHIND(设置暗淡)

4.清除Dialog底背景模糊和黑暗度

getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND | WindowManager.LayoutParams.FLAG_DIM_BEHIND)

46.防止多点触控

android:splitMotionEvents=false
全局禁用:


47.DialogFragment 在5.0手机上适配的各种问题

如果出现弹窗在5.0手机上位置不对,设置Gravity不好使,大小尺寸不对,你可能需要如下设置来解决
1、布局




    

        
    

你需要在内容外部在再套一层,这样弹窗大小的问题就能解决!

2、设置主题

   @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 必须设置,不然会出现5.0及以下手机不适配
        setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogStyle);
    }
    

5.0及以下手机dialogFragment 有默认主题,显示位置不对一般是这个导致的!

48.虚拟导航键显示判断

    public static boolean isNavBarVisible(Context context) {
        ViewGroup rootLinearLayout = findRootLinearLayout(context);
        int navigationBarHeight = 0;
        if (rootLinearLayout != null) {
            ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) rootLinearLayout.getLayoutParams();
            navigationBarHeight = layoutParams.bottomMargin;
        }
        return navigationBarHeight != 0;
    }

    private static ViewGroup findRootLinearLayout(Context context) {
        Activity activity = ActivityUtils.getActivityByContext(context);
        if (activity != null) {
            Window window = activity.getWindow();
            if (window != null) {
                ViewGroup decorView = (ViewGroup) window.getDecorView();
                if (decorView != null) {
                    View contentView = activity.findViewById(android.R.id.content);
                    if (contentView != null) {
                        ViewGroup parent = (ViewGroup) contentView.getParent();
                        while (parent != decorView) {
                            if (parent == null) {
                                return null;
                            }
                            if (parent instanceof LinearLayout && !(parent instanceof FitWindowsLinearLayout)) {
                                return parent;
                            }
                            parent = (ViewGroup) parent.getParent();
                        }
                    }
                }
            }
        }
        return null;
    }

51.recyclerView禁用多指

android:splitmotionevents=”false”
或者
recyclerView.setMotionEventSplittingEnabled(false);

52.recyclerView滑动到指定位置,并指定位置在顶部

1.第一种方法
此方法能实现指定位置位于屏幕顶部,但是不具有平滑滚动视觉效果:

 if (position != -1) {
                    mRecycleview.scrollToPosition(position);
                    LinearLayoutManager mLayoutManager =
                            (LinearLayoutManager) mRecycleview.getLayoutManager();
                    mLayoutManager.scrollToPositionWithOffset(position, 0);
                }

2.第二种方法
此方法能实现指定位置位于屏幕顶部,具有平滑滚动视觉效果:

首先获取第一个可见位置和最后一个可见位置,分三种情况:
1.如果如果跳转位置在第一个可见位置之前,就smoothScrollToPosition()可以直接跳转;
2.如果跳转位置在第一个可见项之后,最后一个可见项之前smoothScrollToPosition()不会滚动,此时调用smoothScrollBy来滑动到指定位置;
3.如果要跳转的位置在最后可见项之后,则先调用smoothScrollToPosition()将要跳转的位置滚动到可见位置,在addOnScrollListener()里通过onScrollStateChanged控制,调用smoothMoveToPosition,再次执行判断;

 //目标项是否在最后一个可见项之后
    private boolean mShouldScroll;
    //记录目标项位置
    private int mToPosition;
    /**
     * 滑动到指定位置
     */
    private void smoothMoveToPosition(RecyclerView mRecyclerView, final int position) {
        // 第一个可见位置
        int firstItem = mRecyclerView.getChildLayoutPosition(mRecyclerView.getChildAt(0));
        // 最后一个可见位置
        int lastItem = mRecyclerView.getChildLayoutPosition(mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1));
        if (position < firstItem) {
            // 第一种可能:跳转位置在第一个可见位置之前
            mRecyclerView.smoothScrollToPosition(position);
        } else if (position <= lastItem) {
            // 第二种可能:跳转位置在第一个可见位置之后
            int movePosition = position - firstItem;
            if (movePosition >= 0 && movePosition < mRecyclerView.getChildCount()) {
                int top = mRecyclerView.getChildAt(movePosition).getTop();
                mRecyclerView.smoothScrollBy(0, top);
            }
        } else {
            // 第三种可能:跳转位置在最后可见项之后
            mRecyclerView.smoothScrollToPosition(position);
            mToPosition = position;
            mShouldScroll = true;
        }
    }

 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (mShouldScroll&& RecyclerView.SCROLL_STATE_IDLE == newState) {
                    mShouldScroll = false;
                    smoothMoveToPosition(irc, mToPosition);
                }
            }
        });
if (position != -1) {
                    smoothMoveToPosition(irc,position);
                }else {
                    smoothMoveToPosition(irc,position+1);
                }

53.RecyclerVIew内的Item布局超出来了?

事情是这样的,项目升级androidx后发现,RecyclerVIew内的Item布局超出来,即外层限制不住它了,相关代码:

 GridLayoutManager.LayoutParams params = (GridLayoutManager.LayoutParams) rvVoiceRoom.getLayoutParams();
            //超过一行需要动态修改左边和底部的距离
            params.setMargins(DisplayUtil.dip2px(getContext(), 14), 0, 0, 0);
            rvVoiceRoom.setLayoutParams(params);
            GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), spanCount, GridLayoutManager.HORIZONTAL, false);
            rvVoiceRoom.setLayoutManager(gridLayoutManager);

明明限制了左边距是14dp,为什么显示效果是直接加在第一个item里了呢,无法限制整个滑动的区域。
查看布局文件发现,这个RecyclerView没有父布局。



由此可以想到,应该是androidx修改了RecyclerView的源码,当RecyclerView没有父布局时,对他的layoutParam(GridLayoutManager.LayoutParams)进行修改,相当于在加在第一个item(最后一个 item里)。
故,修复这个问题也很简单,套一层父布局即可解决,布局修改如下:




    


别忘了修改params的类型

FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) rvVoiceRoom.getLayoutParams();

效果和分析的一样,确实是这个原因。有时间还是比较下源码,Mark!!!todo

54.

你可能感兴趣的:(Android开发小技巧)