Android实用小技巧(持续更新)
文章目录
- 扫描Context获取Activity
- 给drawableRight设置点击事件
- 限制EditText可输入的字数,超出后提示文案
- 解决RecycleView布局中GridLayoutManager和StaggeredGridLayoutManager添加头部和底部不占用一行的问题
- 解决由于RecyclerView有刷新头存在,导致canScrollVertically(-1)时始终返回true的bug
- 获取RecycleView第一个和最后一个可见Item的位置
- 修改RadioButton的drawableRight图片与文字相隔的距离太大,导致drawablePadding设置无效
- 解决RadioButton无法重复点击取消选中的状态
- 解决手动设置Switch的setChecked方法导致setOnCheckedChangeListener触发响应
- 避免SwipeRefreshLayout重复下拉导致刷新按钮异常显示
- TextView的drawableLeft与文本无法一起居中显示
- 控制子View的状态跟随父View状态变化
- 重力传感监听
- 网络状态监听
- RecycleView#LinearLayoutManager滚动到指定位置并距离顶部xxx偏移量
- RecycleView水平向左滑动弹性收回
- 限制EditText的最大字数输入
- 临时申请root权限
- WebView内容截屏
- 修改状态栏为透明
- 修改状态栏背景颜色
- 修改状态栏文字颜色(白/黑切换)
- 代码设置选择器
- 限制EditText小数点后可以输入几位数
- 点击非EditText区域隐藏软键盘
- WebView首页重定向后,canGoBack在首页永远返回true,怎么解决?
- 动态设置shape的圆角
- 禁用多点触控
- 屏蔽dialog的焦点
- View设置缩放中心点
- android如何监听应用进入后台,回到前台时做相应逻辑
- Android 软键盘弹出情况下监听返回键直接退出界面
- 解决App启动时黑屏或者白屏的问题
- android启动页图片icon拉伸问题完美解决方案
- 解决RecycleView#StaggeredGridLayoutManager布局,列表滚动时会出现切换动画的问题
- gradle.properties常用的几个配置说明
- Java 得到泛型中得到T.class
- Android textview字体不随系统字体改变
- 如何设置Dialog的状态栏和虚拟导航栏为透明
- RecycleView的GridLayoutManager布局自适应高度
- 清空ViewPager显示Fragment的时的缓存
扫描Context获取Activity
public static Activity scanForActivity(Context ctx) { if (ctx == null) return null; else if (ctx instanceof Activity) return (Activity) ctx; else if (ctx instanceof ContextWrapper) return scanForActivity(((ContextWrapper) ctx).getBaseContext()); return null;}
一般用于在Dialog中show的时候判断Activity的引用是否有效,例如:
@Overridepublic void show() { Activity activity = UIUtils.scanForActivity(getContext()); if (null != activity && !activity.isFinishing()) super.show();}
给drawableRight设置点击事件
例如EditText右侧有一个删除按钮是通过drawableRight属性设置的,此时如果想让其响应点击事件,这可以通过判断点击的坐标位置与删除按钮的位置对比,下面是通过处理点击删除按钮删除EditText框的内容
/** * 给EditText的右侧drawableRight属性的图片设置点击事件 * * @param editText */public static void registerEditRightDrawableClickListener(final EditText editText) { editText.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // et.getCompoundDrawables()得到一个长度为4的数组,分别表示左上右下四张图片 Drawable drawable = editText.getCompoundDrawables()[2]; //如果右边没有图片,不再处理 if (drawable == null) return false; //如果不是按下事件,不再处理 if (event.getAction() != MotionEvent.ACTION_UP) return false; if (event.getX() > editText.getWidth() - editText.getPaddingRight() - drawable.getIntrinsicWidth()) { editText.setText(""); return true; } return false; } });}
限制EditText可输入的字数,超出后提示文案
/** * 限制EditText可输入的字数,超出后提示文案 * * @param editText 目标view * @param maxLength 最大字数 * @param msg 提示文案 * @param callback 回调接口 */ public static void registerEditMaxTextShow(final EditText editText, final int maxLength, final String msg, final Callback callback) { editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { String currMsg = editText.getText().toString().trim(); if (null != callback) callback.onEditTextChange(currMsg); if (currMsg.length() > maxLength) { ToastUtils.showShort(msg); int editStart = editText.getSelectionStart(); int editEnd = editText.getSelectionEnd(); s.delete(editStart - 1, editEnd); String finalMsg = s.toString(); editText.removeTextChangedListener(this); editText.setText(finalMsg); editText.setSelection(finalMsg.length()); if (null != callback) callback.onEditTextChange(finalMsg); editText.addTextChangedListener(this); } } }); } //回调接口 public interface Callback { void onEditTextChange(String msg); }
解决RecycleView布局中GridLayoutManager和StaggeredGridLayoutManager添加头部和底部不占用一行的问题
重写RecyclerView.Adapter的2个方法
/** * 解决GridLayoutManager添加头部和底部不占用一行的问题 * * @param recyclerView */@Overridepublic void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if (manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return (isHeaderViewPosition(position) || isFooterViewPosition(position)) ? gridManager.getSpanCount() : 1; } }); } mAdapter.onAttachedToRecyclerView(recyclerView);}/** * 解决StaggeredGridLayoutManager添加头部和底部不占用一行的问题 * * @param holder */@Overridepublic void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); int position = holder.getLayoutPosition(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams && (isHeaderViewPosition(position) || isFooterViewPosition(position))) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } mAdapter.onViewAttachedToWindow(holder);}
重写RecycleView的setLayoutManager方法
/** * 解决GridLayoutManager添加头部和底部不占用一行的问题 * * @param layout */@Overridepublic void setLayoutManager(LayoutManager layout) { super.setLayoutManager(layout); if (mWrapAdapter != null) { if (layout instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) layout); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return (mWrapAdapter.isHeaderViewPosition(position) || mWrapAdapter.isFooterViewPosition(position)) ? gridManager.getSpanCount() : 1; } }); } }}
前提是得确定头部和尾部的位置,即isHeaderViewPosition和isFooterViewPosition的逻辑了.通过adapter就可以计算出是否是头部还是尾部的位置了.
解决由于RecyclerView有刷新头存在,导致canScrollVertically(-1)时始终返回true的bug
@Override public boolean canScrollVertically(int direction) { if (direction < 1) { boolean original = super.canScrollVertically(direction); int firstVisiblePosition = getFirstVisiblePosition(); if (!original || getChildAt(0) != null && getChildAt(0).getTop() >= 0) { LayoutManager layoutManager = getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { return firstVisiblePosition > 0; } else if (layoutManager instanceof StaggeredGridLayoutManager) { int spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount(); return firstVisiblePosition > spanCount; } else if (layoutManager instanceof GridLayoutManager) { int spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); return firstVisiblePosition > spanCount; } } else { return true; } } return super.canScrollVertically(direction); }
获取RecycleView第一个和最后一个可见Item的位置
/** * 获取第一个可见的item位置 * @return */ public int getFirstVisiablePosition() { LayoutManager layoutManager = getLayoutManager(); int firstVisibleItemPosition; if (layoutManager instanceof GridLayoutManager) { firstVisibleItemPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(into); firstVisibleItemPosition = findMin(into); } else { firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); } return firstVisibleItemPosition; } /** * 获取可见列表内最后一个item的位置 * * @return */public int getLastVisibleItemPosition() { int lastVisibleItemPosition; LayoutManager layoutManager = getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()]; ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into); lastVisibleItemPosition = findMax(into); } else { lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } return lastVisibleItemPosition;}private int findMin(int[] firstPositions) { int min = firstPositions[0]; for (int value : firstPositions) { if (value < min) { min = value; } } return min; } private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) { if (value > max) { max = value; } } return max; }
修改RadioButton的drawableRight图片与文字相隔的距离太大,导致drawablePadding设置无效
重写AppCompatRadioButton的onDraw方法
@Overrideprotected void onDraw(Canvas canvas) { //得到Drawable集合 分别对应 左上右下 Drawable[] drawables = getCompoundDrawables(); if (drawables != null) { //获取右边图片,修改drawableRight的图片紧贴着文字 Drawable drawableRight = drawables[2]; if (drawableRight != null) { //获取文字占用长宽 int textWidth = (int) getPaint().measureText(getText().toString()); int textHeight = (int) getPaint().getTextSize(); //获取图片实际长宽 int drawableWidth = drawableRight.getIntrinsicWidth(); int drawableHeight = drawableRight.getIntrinsicHeight(); //setBounds修改Drawable在View所占的位置和大小,对应参数同样的 左上右下() int bodyWidth = textWidth + drawableWidth + getCompoundDrawablePadding(); int left = (bodyWidth - getWidth()) / 2; int right = left + drawableWidth; drawableRight.setBounds(left, 0, right, drawableHeight); } } super.onDraw(canvas);}
解决RadioButton无法重复点击取消选中的状态
重写AppCompatRadioButton的toggle方法
@Overridepublic void toggle() { setChecked(!isChecked()); if (!isChecked()) { if (null != getParent() && getParent() instanceof RadioGroup) ((RadioGroup) getParent()).clearCheck(); }}
解决手动设置Switch的setChecked方法导致setOnCheckedChangeListener触发响应
mPushSwt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView.isPressed()) { //避免代码设置setChecked状态导致回调监听 //do sth... } }});
避免SwipeRefreshLayout重复下拉导致刷新按钮异常显示
重写SwipeRefreshLayout的onStartNestedScroll方法
@Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { //避免重复下拉刷新导致动画异常 return !isRefreshing() && super.onStartNestedScroll(child, target, nestedScrollAxes); }
更新于2019-01-03
TextView的drawableLeft与文本无法一起居中显示
TextView设置的文本默认是存在一个上下间距的,也就是上下空白,当我们在使用drawableLeft的时候,这个默认的空白会使TextView中的文本向下偏移,当你的drawableLeft使用的icon很小,文字的size也很小的时候,即使你设置了android:gravity=“center”,也能很明显的看到你的TextView中的文本基本上是与icon处于底边对其,而不是居中对其
只要TextView中加上android:includeFontPadding=“false” 这个属性属性就可以了!
控制子View的状态跟随父View状态变化
代码设置 :
setDuplicateParentStateEnabled(true)
布局设置:
android:duplicateParentState="true"
更新于2019-01-08
重力传感监听
public class MySensorHelper { private static final String TAG = MySensorHelper.class.getSimpleName(); private OrientationEventListener mLandOrientationListener; private OrientationEventListener mPortOrientationListener; public interface Callback { void onOrientationChange(int orientation); } public MySensorHelper(final Activity activity, final Callback callback) { this.mLandOrientationListener = new OrientationEventListener(activity, 3) { public void onOrientationChanged(int orientation) { Log.d(MySensorHelper.TAG, "mLandOrientationListener"); if (orientation < 100 && orientation > 80 || orientation < 280 && orientation > 260) { Log.e(MySensorHelper.TAG, "转到了横屏"); if (callback != null) { Log.e(MySensorHelper.TAG, "转到了横屏##################"); callback.onOrientationChange(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } } }; this.mPortOrientationListener = new OrientationEventListener(activity, 3) { public void onOrientationChanged(int orientation) { Log.w(MySensorHelper.TAG, "mPortOrientationListener"); if (orientation < 10 || orientation > 350 || orientation < 190 && orientation > 170) { Log.e(MySensorHelper.TAG, "转到了竖屏"); if (callback != null) { Log.e(MySensorHelper.TAG, "转到了竖屏!!!!!!!!!!!!!!!!!!!!!!"); callback.onOrientationChange(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } } } }; this.enable(); } //禁用切换屏幕的开关 public void disable() { Log.e(TAG, "disable"); this.mPortOrientationListener.disable(); this.mLandOrientationListener.disable(); } //开启横竖屏切换的开关 public void enable() { this.mPortOrientationListener.enable(); this.mLandOrientationListener.enable(); }}
网络状态监听
public class NetworkConnectChangedReceiver extends BroadcastReceiver { private static final String TAG = "Network"; private Callback mCallback; public abstract static class Callback { /** * wifi是否打开 * * @param isEnable */ public void isWifiEnable(boolean isEnable) { } /** * 各种状态监听 * * @param wifi * @param mobile * @param all */ public void isAvailable(boolean wifi, boolean mobile, boolean all) { } } public NetworkConnectChangedReceiver(Callback callback) { this.mCallback = callback; } public IntentFilter getIntentFilter() { IntentFilter filter = new IntentFilter(); filter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); filter.addAction("android.net.wifi.WIFI_STATE_CHANGED"); filter.addAction("android.net.wifi.STATE_CHANGE"); return filter; } @Override public void onReceive(Context context, Intent intent) { // 这个监听wifi的打开与关闭,与wifi的连接无关 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0); Log.e(TAG, "wifiState" + wifiState); switch (wifiState) { case WifiManager.WIFI_STATE_DISABLED: mCallback.isWifiEnable(false); break; case WifiManager.WIFI_STATE_DISABLING: break; case WifiManager.WIFI_STATE_ENABLING: break; case WifiManager.WIFI_STATE_ENABLED: mCallback.isWifiEnable(true); break; case WifiManager.WIFI_STATE_UNKNOWN: break; default: break; } } if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { ConnectivityManager manager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); Log.i(TAG, "CONNECTIVITY_ACTION"); NetworkInfo activeNetwork = manager.getActiveNetworkInfo(); if (activeNetwork != null) { // connected to the internet if (activeNetwork.isConnected()) { if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { // connected to wifi Log.e(TAG, "当前WiFi连接可用 "); mCallback.isAvailable(true, false, true); } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { // connected to the mobile provider's data plan Log.e(TAG, "当前移动网络连接可用 "); mCallback.isAvailable(false, true, true); } } else { Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 "); mCallback.isAvailable(false, false, false); } } else { // not connected to the internet Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 "); mCallback.isAvailable(false, false, false); } } }}
使用方式
private NetworkConnectChangedReceiver mConnectChangedReceiver;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mConnectChangedReceiver = new NetworkConnectChangedReceiver(new NetworkConnectChangedReceiver.Callback() { @Override public void isAvailable(boolean wifi, boolean mobile, boolean all) { if (wifi) { //dosth.. }else if(mobile){ //dosth...} } }); registerReceiver(mConnectChangedReceiver, mConnectChangedReceiver.getIntentFilter());}@Overrideprotected void onDestroy() { super.onDestroy(); unregisterReceiver(mConnectChangedReceiver);}
更新于2019-01-10
RecycleView#LinearLayoutManager滚动到指定位置并距离顶部xxx偏移量
LinearLayoutManager layoutManager = (LinearLayoutManager) mRv.getLayoutManager(); int itemWidth = DisplayUtils.dip2px(mContext, 51);//item的宽度 //item位于中心显示的偏移量 int pinkPx = (int) ((Env.screenWidth - 2 * mRv.getPaddingLeft() - itemWidth) * 0.5f); //定位到指定的position并且该position对应的item距离RecycleView顶部的距离是pinkPx layoutManager.scrollToPositionWithOffset(currIndex, pinkPx);
#更新于2019-01-17
RecycleView水平向左滑动弹性收回
重写RecycleView的onTouchEvent方法
private float downTouch; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downTouch = event.getX(); break; case MotionEvent.ACTION_MOVE: float moveTouch = event.getX(); if (!canScrollHorizontally(1)) { int deltaX = (int) (downTouch - moveTouch); if (deltaX > 10) { //向左滑动到底后,修改其右内边距不断递增 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight() + deltaX / 3, getPaddingBottom()); } } else { //恢复原位 setPadding(getPaddingLeft(), getPaddingTop(), 0, getPaddingBottom()); } downTouch = moveTouch; break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: //恢复原位 setPadding(getPaddingLeft(), getPaddingTop(), 0, getPaddingBottom()); break; } return super.onTouchEvent(event); }
如果还需要实现回滚完毕后跳去新页面,则可以监听其滚动,当滚动停止的时候就可以跳去新页面了.
例如:
OverScrollRecycleView osRv = findViewById(R.id.rv_over_scroll);osRv.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView rv, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE && !rv.canScrollHorizontally(1)) { Intent intent = new Intent(mContext, NextActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//避免打开同一个Activity多次 startActivity(intent); } }});
#更新于2019-01-24
限制EditText的最大字数输入
例如限制EditText最大只能输入150个字,超出后提示文案
private class LengthInputFilter extends InputFilter.LengthFilter { public LengthInputFilter(int max) { super(max); } @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { CharSequence s = super.filter(source, start, end, dest, dstart, dend); if (s != null) { //getMax是LengthFilter的内部方法,返回的是构造方法传入的max ToastUtils.show(mContext, "不能超过" + getMax()+ "字", Toast.LENGTH_SHORT); } return s; } }
使用方式如下:
mInputEdt.setFilters(new InputFilter[]{new LengthInputFilter(150)});
更新于2019-01-26
临时申请root权限
前提是设备已经root过了.
public void testRoot() { try { Process su = Runtime.getRuntime().exec("su"); OutputStream outputStream = su.getOutputStream(); DataOutputStream dataOutputStream = new DataOutputStream(outputStream); dataOutputStream.writeBytes("\n"); dataOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }
更新于2019-01-30
WebView内容截屏
/** * Android 5.0以下版本 * 此方法用在5.0以上时需在创建webView之前调用enableSlowWholeDocumentDraw()方法关闭优化才能截取到这个WebView内容 * 对WebView进行截屏 * * @param webView * @return */ public static Bitmap getWebViewLongBitmpKitKat(WebView webView) { Picture picture = webView.capturePicture(); int width = picture.getWidth(); int height = picture.getHeight(); if (width > 0 && height > 0) { Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), (int) (webView.getContentHeight() * webView.getScale()), Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); picture.draw(canvas); return bitmap; } return null; }
修改状态栏为透明
/** * 修改状态栏为透明效果 */ public static void setStatusBarTrans(Activity activity) { //5.0及以上,修改完后,布局会延伸到状态栏 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = activity.getWindow(); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.setStatusBarColor(Color.TRANSPARENT); } //4.4到5.0 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { WindowManager.LayoutParams localLayoutParams = activity.getWindow().getAttributes(); localLayoutParams.flags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags; } }
修改状态栏背景颜色
/** * 修改状态栏颜色,支持4.4以上版本 * * @param activity * @param colorId */ public static void setStatusBarColor(Activity activity, int colorId) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = activity.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(activity.getResources().getColor(colorId)); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明 WindowManager.LayoutParams localLayoutParams = activity.getWindow().getAttributes(); localLayoutParams.flags = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags;//需要引入依赖包 compile 'com.readystatesoftware.systembartint:systembartint:1.0.3' SystemBarTintManager tintManager = new SystemBarTintManager(activity); tintManager.setStatusBarTintEnabled(true); tintManager.setStatusBarTintResource(colorId); } }
修改状态栏文字颜色(白/黑切换)
/** * android 4.4及以上修改状态栏文字的颜色 * 修改状态栏文字颜色,这里小米,魅族区别对待。 */ public static void setLightStatusBar(final Activity activity, final boolean dark) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { switch (RomUtils.getLightStatusBarAvailableRomType()) { case RomUtils.AvailableRomType.MIUI: setMIUIStatusBarLightMode(activity, dark); break; case RomUtils.AvailableRomType.FLYME: setFlymeLightStatusBar(activity, dark); break; default: setAndroidNativeLightStatusBar(activity, dark); break; } } } /** * 小米系统下状态栏文字颜色的修改 * * @param activity * @param dark true:黑色 false: 白色 * @return */ public static boolean setMIUIStatusBarLightMode(Activity activity, boolean dark) { boolean result = false; Window window = activity.getWindow(); if (window != null) { Class clazz = window.getClass(); try { int darkModeFlag = 0; Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); darkModeFlag = field.getInt(layoutParams); Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); if (dark) { extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体 } else { extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体 } result = true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && RomUtils.isMiUIV7OrAbove()) { //开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上 if (dark) { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } else { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); } } } catch (Exception e) { } } return result; } /** * 魅族系统状态栏文字颜色修改 * * @param activity * @param dark true:黑色 false: 白色 * @return */ private static boolean setFlymeLightStatusBar(Activity activity, boolean dark) { boolean result = false; if (activity != null) { try { WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); Field darkFlag = WindowManager.LayoutParams.class .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); Field meizuFlags = WindowManager.LayoutParams.class .getDeclaredField("meizuFlags"); darkFlag.setAccessible(true); meizuFlags.setAccessible(true); int bit = darkFlag.getInt(null); int value = meizuFlags.getInt(lp); if (dark) { value |= bit; } else { value &= ~bit; } meizuFlags.setInt(lp, value); activity.getWindow().setAttributes(lp); result = true; } catch (Exception e) { } } return result; } /** * 修改状态栏文字颜色 * @param activity * @param dark true:深色,false:白色 */ private static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) { View decor = activity.getWindow().getDecorView(); if (dark) { //设置字体为深色 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //6.0+ decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); }else{ //6.0以下无法修改状态栏文字的颜色,只能通过修改状态栏颜色为暗色来区分白色的字体 setStatusBarColor(activity, R.color.color_10Black); //调用前面例子的方法 } } else { //恢复字体为白色 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //6.0+ decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); }else{ //6.0以下无法修改状态栏文字的颜色,默认就是白色,将之前设置状态栏的颜色去掉 setStatusBarColor(activity, R.color.full_transparent); } } }
更新于2019-02-28
代码设置选择器
1.文字颜色选择器
//选择器状态是二维数组,负数表示false,正数表示trueint[][] states = new int[][]{ new int[]{-android.R.attr.state_selected}, // unselected new int[]{android.R.attr.state_selected} // selected }; //对应状态的颜色值 int[] colors = new int[]{ getResources().getColor(R.color.color_121C35), getResources().getColor(R.color.color_FF3B3B) }; //设置文字颜色选择器ColorStateList TextView textView = new TextView(getContext()); textView.setTextColor(new ColorStateList(states, colors));
2.图片背景选择器
//创建图片选择器StateListDrawable stateListDrawable = new StateListDrawable(); //添加状态和对应的图片,同样状态是负数表示false,反之表示true stateListDrawable.addState(new int[]{-android.R.attr.state_checked}, getDrawable(android.R.drawable.ic_item_normal)); stateListDrawable.addState(new int[]{android.R.attr.state_checked}, getDrawable(android.R.drawable.ic_item_checked)); //直接设置背景为图片选择器 ImageView imageView = new ImageView(getContext()); imageView.setImageDrawable(stateListDrawable);
更新于2019-03-05
限制EditText小数点后可以输入几位数
/** * 保留多少位小数 * * @param expectPriceEdt * @param remainCount 保留几位小数,例如保留2位小数,那么小数点后只能输入2位数 */ public static void formatNumInput(EditText expectPriceEdt, final int remainCount) { expectPriceEdt.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable edt) { String temp = edt.toString(); int posDot = temp.indexOf("."); if (posDot <= 0) return; if (temp.length() - posDot - 1 > remainCount) { edt.delete(posDot + 3, posDot + 4); } } public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { } }); }
点击非EditText区域隐藏软键盘
//点击其他区域隐藏软键盘后还是否需要响应当次的点击事件 private boolean clickShouldRspAfterSoftInputHide;//重写Activity的事件分发方法,点击输入框以外的区域隐藏软键盘 @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { View v = getCurrentFocus(); if (isShouldHideInput(v, ev)) { if (hideInputMethod(v) && !clickShouldRspAfterSoftInputHide) { /**直接返回true表示不会向下继续分发事件了 如果点击的view还需要响应事件,那么clickShouldRspAfterSoftInputHide设置为true **/ return true; } } } return super.dispatchTouchEvent(ev); }//判断是否要隐藏软键盘 public boolean isShouldHideInput(View v, MotionEvent event) { if (v != null && (v instanceof EditText)) { int[] leftTop = {0, 0}; v.getLocationInWindow(leftTop);//获取控件在屏幕的坐标 int left = leftTop[0], top = leftTop[1], bottom = top + v.getHeight(), right = left + v.getWidth(); if (event.getRawX() > left && event.getRawX() < right && event.getRawY() > top && event.getRawY() < bottom) { // 点击的范围是当前EditText,不用隐藏软键盘 return false; } else { return true;//需要隐藏软键盘 } } return false; } public Boolean hideInputMethod(View v) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { return imm.hideSoftInputFromWindow(v.getWindowToken(), 0); } return false; }
更新于2019-03-07
WebView首页重定向后,canGoBack在首页永远返回true,怎么解决?
@Override public void onBackPressed() { //后退 if (mWebView.canGoBack()) { WebBackForwardList list = mWebView.copyBackForwardList(); if (list.getCurrentIndex() ==0) { //如果首页重定向后,初始地址会变成data:text/html;charset=utf-8;base64, String firstUrl = list.getItemAtIndex(0).getUrl(); if (!URLUtil.isNetworkUrl(firstUrl)) { //避免首次进入重定向的时候退不出webview super.onBackPressed(); return; } } mWebView.goBack(); } else { super.onBackPressed(); } }
更新于2019-03-08
动态设置shape的圆角
假设布局里面已经给View设置了一个shape作为背景图片了,如果我想在代码中修改它的圆角的话,可以通过这种方式修改:
//圆角为8dp final int dp8 = DisplayUtils.dip2px(mContext, 8); //先找到设置了shape的viewConstraintLayout item= findViewById(R.id.cl_content);//获取shape图形GradientDrawable drawable = (GradientDrawable) item.getBackground();//左上,右上,右下,左下 必须传8个值,每2个为一个方向drawable.setCornerRadii(new float[]{dp8, dp8, dp8, dp8, dp8, dp8, dp8, dp8});
更新于2019-03-13
禁用多点触控
两种方式:
1、禁用全局多点触控:
在application引用的Theme中添加以下代码:
<item name="android:windowEnableSplitTouch">falseitem> <item name="android:splitMotionEvents">falseitem>
2、如果要单独对某个界面禁用,则需要的在相应的xml或代码中添加:
android:splitMotionEvents="false"
或
rootLayout.setMotionEventSplittingEnabled(false);
注意:必须是需要禁止多点触控的控件的上层父类中添加! 父类的父类加了无效。 另外,该属性实在Android API level 11 之后才有的。
更新于2019-03-25
屏蔽dialog的焦点
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
View设置缩放中心点
用过ScaleAnimation的人都知道,可以直接在构造方法中设置,但是如果是使用ObjectAnimator或者是直接setScaleX和setScaleY的方式那要怎么设置缩放中心点呢??
答案是使用View的setPivotX和setPivotY方法单独设置,源码如下:
/** * Sets the x location of the point around which the view is * {@link #setRotation(float) rotated} and {@link #setScaleX(float) scaled}. * By default, the pivot point is centered on the object. * Setting this property disables this behavior and causes the view to use only the * explicitly set pivotX and pivotY values. * * @param pivotX The x location of the pivot point. * @see #getRotation() * @see #getScaleX() * @see #getScaleY() * @see #getPivotY() * * @attr ref android.R.styleable#View_transformPivotX */ public void setPivotX(float pivotX) { if (!mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) { invalidateViewProperty(true, false); mRenderNode.setPivotX(pivotX); invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); } } /** * Sets the y location of the point around which the view is {@link #setRotation(float) rotated} * and {@link #setScaleY(float) scaled}. By default, the pivot point is centered on the object. * Setting this property disables this behavior and causes the view to use only the * explicitly set pivotX and pivotY values. * * @param pivotY The y location of the pivot point. * @see #getRotation() * @see #getScaleX() * @see #getScaleY() * @see #getPivotY() * * @attr ref android.R.styleable#View_transformPivotY */ public void setPivotY(float pivotY) { if (!mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) { invalidateViewProperty(true, false); mRenderNode.setPivotY(pivotY); invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); } }
例如设置右下角为缩放中心点可以这样弄target.setPivotX(target.getWidth());target.setPivotY(target.getHeight());
更新于2019-04-15
android如何监听应用进入后台,回到前台时做相应逻辑
详情看这里
更新于2019-04-17
Android 软键盘弹出情况下监听返回键直接退出界面
详情看这里
更新于2019-04-23
解决App启动时黑屏或者白屏的问题
将启动图通过样式的方式设置
<style name="AppTheme.Splash" parent="@style/Theme.AppCompat.NoActionBar"> - "android:windowFullscreen"
>true - "android:windowNoTitle">true
- "android:windowBackground">@drawable/app_launcher_360
- "android:background">@null
style>
然后在启动Activity使用该样式
<activity android:name=".module.main.LaunchActivity" android:alwaysRetainTaskState="true" android:screenOrientation="portrait" android:theme="@style/AppTheme.Splash"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> intent-filter>activity>
然后LaunchActivity就不需要做任何操作了,如果启动图还有广告图的话,那只需要在布局中添加广告图的布局,代码中书写广告的
加载逻辑就可以了,无需关心默认的启动图,因为广告图显示的时候会自动覆盖默认的的启动图.
例如:
protected void initView() { mTimerTv = findViewById(R.id.tv_timer);//计时器 mLaunchAdIv = findViewById(R.id.iv_launch_ad);//启动图广告 if (null != mLaunchAdv && URLUtil.isNetworkUrl(mLaunchAdv.image)) { ImageLoadUtils.disPlay(mLaunchAdv.image, mLaunchAdIv);//加载启动图 showLaunchAd(); //显示启动图布局 } else { dealLaunchNormal();//处理正常跳转 }}//正常跳转private void dealLaunchNormal() { if (isFirstInstall) { //首次安装 jump2Guide(); //进入新手引导 } else { jump2Main(); //进入首页 }}
更新于2019-04-23
android启动页图片icon拉伸问题完美解决方案
查看详情
更新于2019-04-26
解决RecycleView#StaggeredGridLayoutManager布局,列表滚动时会出现切换动画的问题
由于瀑布流布局,每个item的高度都是不定的,所以当滑动了很多页的时候,滚出屏幕的item已经回收掉了,而当再次滑动回到列表顶部的时候,由于item需要重新布局,所以会看到切换动画, 解决办法如下:
步骤一: 去掉切换动画
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE); //禁止瀑布流切换动画,问题是顶部会留白mRecyclerView.setLayoutManager(layoutManager);mRecyclerView.setAdapter(new MyAdapter(mContext, mData));
步骤二:解决顶部留白的问题
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { boolean hasNotify = true; int topLastVisibleItemPosition = -1;//上次刷新时,最后一个可见item的位置 @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); //获取实时滚动的列表top和bottom可见位置 int firstVisiblePosition = mRecyclerView.getFirstVisiblePosition(); int lastVisibleItemPosition = mRecyclerView.getLastVisibleItemPosition(); //解决顶部留白的问题 if (mRecyclerView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE && !mRecyclerView.canScrollVertically(-1)) { if (mRecyclerView.getAdapter() != null && !hasNotify) { hasNotify = true; topLastVisibleItemPosition = lastVisibleItemPosition; //刷新列表,留白就看不到了. mRecyclerView.getAdapter().notifyDataSetChanged(); LogUtils.e("cys", "置顶刷新"); } } // 当前列表最后一个可见item的位置如果大于上次刷新时的位置,所以列表已经向上拖拽过了,这个时候才需要重置变量hasNotify if (firstVisiblePosition > topLastVisibleItemPosition) { hasNotify = false; //避免重复刷新列表 LogUtils.e("cys", "重置hasNotify = false"); } }});
更新于2019/05/16
gradle.properties常用的几个配置说明
org.gradle.jvmargs=-Xmx1536m : 用来加快gradle的编译!
android.enableAapt2=false
Error:java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception:
android.useDeprecatedNdk=true
Error:(12, 0) Error: NDK integration is deprecated in the current plugin.
Consider trying the new experimental plugin.
For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental.
Set “android.useDeprecatedNdk=true” in gradle.properties to continue using the current NDK integration.
android.injected.testOnly=false
让debug包也可以下载安装.
Java 得到泛型中得到T.class
Class <T> entityClass = (Class <T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
更新于2019/06/4
Android textview字体不随系统字体改变
方式1:把textview的字体大小设置为PX为单位,原因是px不随系统而改变。
方式2:在BaseActivity重写 getResources()方法,具体代码如下
//字体不随系统字体改变@Override public Resources getResources() { Resources resources = super.getResources(); Configuration config = new Configuration(); config.setToDefaults(); resources.updateConfiguration(config, resources.getDisplayMetrics()); return resources; }
更新于2019/06/12
如何设置Dialog的状态栏和虚拟导航栏为透明
方式一:
在style文件中添加自定义样式,并继承@android:style/Theme.Dialog
<item name="android:windowTranslucentStatus">trueitem><item name="android:statusBarColor">@android:color/transparentitem><item name="android:windowTranslucentNavigation">trueitem>
然后,自定义的Dialog的构造方法中使用该样式
super(context, R.style.myDialog);
方式二:
代码设置,在自定义的Dialog中加入这段代码
Window window = getWindow();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //导航栏透明 window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); //状态栏透明,这个其实也可以不用设置,上面那条flag也有这个效果 window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);}
需要强调一点的是,导航栏是在App页面的最上面的,所以透明后,是可以看到dialog的背景的.
更新于2019/06/27
RecycleView的GridLayoutManager布局自适应高度
package com.juchaozhi.classification;import android.content.Context;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.View;import android.view.ViewGroup;/** * 自适应高度的GridLayoutManager * Created by mChenys on 2019/7/24. */public class WrapHeightGridLayoutManager extends GridLayoutManager { private int mChildPerLines;//每一行的子View 个数 private int[] mMeasuredDimension = new int[2]; public WrapHeightGridLayoutManager(Context context, int spanCount) { super(context, spanCount); this.mChildPerLines = spanCount; } @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int height = 0; for (int i = 0; i < getItemCount(); ) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); height = height + mMeasuredDimension[1];//累计每一行的高度 i = i + mChildPerLines; } if (height < heightSize) { switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } setMeasuredDimension(widthSize, height + getPaddingTop() + getPaddingBottom()); } else { // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view. super.onMeasure(recycler, state, widthSpec, heightSpec); } } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); // For adding Item Decor Insets to view super.measureChildWithMargins(view, 0, 0); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view), p.height); view.measure(childWidthSpec, childHeightSpec); // Get decorated measurements measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin; measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin; recycler.recycleView(view); } }}
更新于2019/07/27
清空ViewPager显示Fragment的时的缓存
if (mContentVp.getAdapter() != null) { Class<? extends FragmentManager> aClass = getChildFragmentManager().getClass(); try { Field f = aClass.getDeclaredField("mAdded"); f.setAccessible(true); ArrayList<Fragment> list = (ArrayList) f.get(getChildFragmentManager()); list.clear(); f = aClass.getDeclaredField("mActive"); f.setAccessible(true); SparseArray<Fragment> array = (SparseArray) f.get(getChildFragmentManager()); array.clear(); } catch (Exception e) { e.printStackTrace(); } }
这个得从FragmentPagerAdapter的instantiateItem方法查看
public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment;}
定位到这一句mFragmentManager.findFragmentByTag(name);
,继续查找FragmentManager的实现类FragmentManagerImpl
中的findFragmentByTag方法
public Fragment findFragmentByTag(String tag) { if (tag != null) { // First look through added fragments. for (int i=mAdded.size()-1; i>=0; i--) { Fragment f = mAdded.get(i); if (f != null && tag.equals(f.mTag)) { return f; } } } if (mActive != null && tag != null) { // Now for any known fragment. for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.valueAt(i); if (f != null && tag.equals(f.mTag)) { return f; } } } return null;}
观察mAdded和mActive 变量,他们就是用来存储Fragment的容器
final ArrayList<Fragment> mAdded = new ArrayList<>();SparseArray<Fragment> mActive;
所以如果想要ViewPager重新初始化已加载的Fragment的话,就得通过反射清空这2个容器.
更新于2019/08/7
更多相关文章
- Android(安卓)7.0 Audio :通话中的音频调用接口
- CMake相关问题解决记录
- eclipse 中设置android emulator 选项
- Android(安卓)TextView当中设置超链接、颜色、字体
- 无法在Android(安卓)Studio中设置断点
- 安卓新技术学习资料整理 常更新哦~
- android Application Component研究之Activity(一)
- 14天学会安卓开发(第十一天)Android图形技术
- android Gradle例如:“style attribute 'android:attr/keyboardNa