文章目录

  • 给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的时的缓存
  • shape只有上下边框(shape只有任意一边边框)
  • android View 截屏
  • ViewPager获取当前显示页面的View
  • 保存相片到手机相册
  • 递归删除目录及其所有子目录内容
  • getLocationInWindow 和 getLocationOnScreen区别
  • getWindowVisibleDisplayFrame的用法
  • DecorView的介绍
  • 保留2位小数
  • 软键盘弹出自动时滑动到指定View的下方显示
  • exoplayer获取当前帧画面
  • 通过adb shell命令查看当前与用户交互的activity
  • Android SpannableString 自定义圆角背景
  • RecyclerView获取滚动距离
  • 解析动态style的属性
  • 跳应用市场和搜索页
  • 将含有其他aar包的module打包成新的aar
  • 通过RecyclerView和item获取对应的ViewHolder
  • 通过RecyclerView的findChildViewUnder(x,y)返回指定位置的childView
  • 关闭RecyclerView 的测量、布局操作
  • 通过RecyclerView的findContainingItemView(view)得到包含指定View的ItemView
  • 全屏并让视图延伸到刘海区域
  • SmartRefreshLayout自定义刷新头
  • 将图片绘制到指定大小的View上面
  • 获取Bitmap指定区域和大小的内容
  • IdleHandler (闲时机制)
  • registerForActivityResult的使用
  • 谷歌浏览器调试移动端h5页面
  • transitive和changing的含义
  • gradle 每次运行都会下载依赖的解决办法
  • 同时兼容高本gradle和低版本gradle插件版本
  • multiDexKeepFile属性 和multiDexKeepProguard属性的区别
  • build.gradle配置中packagingOptions{}选项说明
  • gradle配置资源路径
  • Android library打包生成jar包
  • cmd命令行中logcat输出日志中文乱码
  • 从未安装的APK中获取默认启动的Activity
  • 使用aapt2 修改apk打包的资源id
  • 使用AS查看framework源码
  • BottomNavigationView 底部导航栏加入 红点数字提示
  • TabLayout去掉点击的默认背景色
  • Android9,10反射限制问题分析以及解决
  • Gradle强制刷新单模块依赖
  • android:clipToPadding的使用
  • RecyclerView嵌套RecyclerView的内存复用
  • indexOf(String str, int fromIndex)和lastIndexOf(String str, int fromIndex)区别
  • 获取TextView指定span的起点和终点的x坐标
  • 图片Uri转File
  • toolbar修改返回按键颜色
  • Kotlin自动findViewById神器-LayoutContainer
  • RecycleView的layoutManager.setStackFromEnd(true);layoutManager.setReverseLayout(true);
  • Android 设置TextView自动调整字体大小
  • Java正则表达式Pattern.quote()方法详解
  • 按钮水波纹效果
  • 扫描Context获取Activity
  • 巧用Z值修改View的层级
  • 给ImageView的图片上色
  • adjustViewBounds使ImageView和图片比例保持一致
  • Android 更换字体和改变字重
  • TextView设置高亮点击
  • 巧妙利用rotationY属性处理阿语和非阿语环境下返回箭头的方向
  • AppBarLayout折叠后底部有阴影
  • CollapsingToolbarLayout折叠后背景色
  • 去掉TextView的透明色对emoji表情的影响
  • 判断AppBarLayout的折叠状态
  • getChildDrawingOrder实现View层级的倒序排列
  • 巧用ImageSwitcher实现图片的切换
  • 巧用OnBackPressedCallback实现fragment监听Activity的返回键
  • Android 关于Fragment重叠问题分析和解决
  • 巧用FragmentTransaction的setMaxLifecycle方法触发Fragment的生命周期方法
  • 巧用Layout.getDesiredWidth获取TextView的文字宽度
  • Activity作为弹窗,设置背景透明
  • 修改BottomSheetDialogFragment的Theme
  • 判断当前页面是否有弹窗展示
  • 代码中使用点9图
  • 给TextView设置背景图作为Span
  • Android ViewBinding include怎么玩
  • Android ViewBinding viewStub怎么玩
  • 如何获取系统组件的属性
  • 如何设置TextView的互动条
  • 控制AppBarLayout子View的滚动
  • 如何在Android P不受限制的使用反射
  • 设置弹窗的层级
  • Android TextView 跑马灯效果
  • 如何代码设置textView的style
  • 解决FragmentNavigator切换Fragment重建的问题
  • 利用Glide的API获取本地缓存的图片
  • 如何给图片添加蒙层
  • 如何实现第三方App的文本分享

给drawableRight设置点击事件

例如EditText右侧有一个删除按钮是通过drawableRight属性设置的,此时如果想让其响应点击事件,这可以通过判断点击的坐标位置与删除按钮的位置对比,下面是通过处理点击删除按钮删除EditText框的内容

/*** 给EditText的右侧drawableRight属性的图片设置点击事件** @param editText*/
public static void registerEditRightDrawableClickListener(final EditText editText) {editText.setOnTouchListener(new View.OnTouchListener() {@Overridepublic 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() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {}@Overridepublic 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*/
@Override
public 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() {@Overridepublic int getSpanSize(int position) {return (isHeaderViewPosition(position) || isFooterViewPosition(position))? gridManager.getSpanCount() : 1;}});}mAdapter.onAttachedToRecyclerView(recyclerView);
}
/*** 解决StaggeredGridLayoutManager添加头部和底部不占用一行的问题** @param holder*/
@Override
public 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*/
@Override
public void setLayoutManager(LayoutManager layout) {super.setLayoutManager(layout);if (mWrapAdapter != null) {if (layout instanceof GridLayoutManager) {final GridLayoutManager gridManager = ((GridLayoutManager) layout);gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {@Overridepublic int getSpanSize(int position) {return (mWrapAdapter.isHeaderViewPosition(position) || mWrapAdapter.isFooterViewPosition(position))? gridManager.getSpanCount() : 1;}});}}
}

前提是得确定头部和尾部的位置,即isHeaderViewPosition和isFooterViewPosition的逻辑了.通过adapter就可以计算出是否是头部还是尾部的位置了.


解决由于RecyclerView有刷新头存在,导致canScrollVertically(-1)时始终返回true的bug

@Overridepublic 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方法

@Override
protected 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方法

@Override
public void toggle() {setChecked(!isChecked());if (!isChecked()) {if (null != getParent() && getParent() instanceof RadioGroup)((RadioGroup) getParent()).clearCheck();}
}

解决手动设置Switch的setChecked方法导致setOnCheckedChangeListener触发响应

mPushSwt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if (buttonView.isPressed()) { //避免代码设置setChecked状态导致回调监听//do sth...}}
});

避免SwipeRefreshLayout重复下拉导致刷新按钮异常显示

重写SwipeRefreshLayout的onStartNestedScroll方法

 @Overridepublic 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;}@Overridepublic 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 internetif (activeNetwork.isConnected()) {if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {// connected to wifiLog.e(TAG, "当前WiFi连接可用 ");mCallback.isAvailable(true, false, true);} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {// connected to the mobile provider's data planLog.e(TAG, "当前移动网络连接可用 ");mCallback.isAvailable(false, true, true);}} else {Log.e(TAG, "当前没有网络连接,请确保你已经打开网络 ");mCallback.isAvailable(false, false, false);}} else {   // not connected to the internetLog.e(TAG, "当前没有网络连接,请确保你已经打开网络 ");mCallback.isAvailable(false, false, false);}}}}

使用方式

private NetworkConnectChangedReceiver mConnectChangedReceiver;@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);mConnectChangedReceiver = new NetworkConnectChangedReceiver(new NetworkConnectChangedReceiver.Callback() {@Overridepublic void isAvailable(boolean wifi, boolean mobile, boolean all) {if (wifi) {//dosth..}else if(mobile){//dosth...}}});registerReceiver(mConnectChangedReceiver, mConnectChangedReceiver.getIntentFilter());
}@Override
protected 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顶部的距离是pinkPxlayoutManager.scrollToPositionWithOffset(currIndex, pinkPx);

#更新于2019-01-17


RecycleView水平向左滑动弹性收回

重写RecycleView的onTouchEvent方法

private float downTouch;@Overridepublic 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() {@Overridepublic 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);}@Overridepublic 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的内部方法,返回的是构造方法传入的maxToastUtils.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.0else 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}, // unselectednew int[]{android.R.attr.state_selected}  // selected};//对应状态的颜色值int[] colors = new int[]{getResources().getColor(R.color.color_121C35),getResources().getColor(R.color.color_FF3B3B)};//设置文字颜色选择器ColorStateListTextView textView = new TextView(getContext());textView.setTextColor(new ColorStateList(states, colors));

2.图片背景选择器

 //创建图片选择器StateListDrawable stateListDrawable = new StateListDrawable();//添加状态和对应的图片,同样状态是负数表示false,反之表示truestateListDrawable.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的事件分发方法,点击输入框以外的区域隐藏软键盘@Overridepublic 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,怎么解决?

 @Overridepublic 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)) {//避免首次进入重定向的时候退不出webviewsuper.onBackPressed();return;}}mWebView.goBack();} else {super.onBackPressed();}}

更新于2019-03-08


动态设置shape的圆角

假设布局里面已经给View设置了一个shape作为背景图片了,如果我想在代码中修改它的圆角的话,可以通过这种方式修改:

 //圆角为8dpfinal 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">false</item><item name="android:splitMotionEvents">false</item>

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"><item name="android:windowFullscreen">true</item><item name="android:windowNoTitle">true</item><!--开屏图,解决应用启动的时候,会白屏一段时间的问题--><item name="android:windowBackground">@drawable/app_launcher_360</item><!--就是这个控件的背景,注意这个属性会影响到dialog的背景--><item name="android:background">@null</item></style>

然后在启动Activity使用该样式

<activityandroid: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的位置@Overridepublic 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的位置如果大于上次刷新时的位置,所以列表已经向上拖拽过了,这个时候才需要重置变量hasNotifyif (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()方法,具体代码如下

//字体不随系统字体改变
@Overridepublic 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">true</item>
<!--状态栏颜色设置成透明,这个属性是v21可以用,单独在values-v21下创建style-->
<item name="android:statusBarColor">@android:color/transparent</item>
<!--导航透明化-->
<item name="android:windowTranslucentNavigation">true</item>

然后,自定义的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;}@Overridepublic 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 viewsuper.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 measurementsmeasuredDimension[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


shape只有上下边框(shape只有任意一边边框)

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"><!--    只有上下边框的shape    --><item><shape><!--    边框颜色    --><solid android:color="@color/color_d6d6d6"/></shape></item><!--    想保留哪一边就设置哪一边边框就好了,列如只有上边框:top="1dp"    --><item android:top="1dp" android:bottom="1dp"><shape><solid android:color="@color/color_ffffff"/></shape></item>
</layer-list>

android View 截屏

详情


ViewPager获取当前显示页面的View

在ViewPager的PagerAdapter里面有一个回调方法,源码如下:

/*** Called to inform the adapter of which item is currently considered to* be the "primary", that is the one show to the user as the current page.** @param container The containing View from which the page will be removed.* @param position The page position that is now the primary.* @param object The same object that was returned by* {@link #instantiateItem(View, int)}.*/
public void setPrimaryItem(ViewGroup container, int position, Object object) {setPrimaryItem((View) container, position, object);
}

我们只需要重写该方法,然后记录当前的View

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {this.mCurrentView = (View) object;
}

之后再对外提供一个方法获取该View即可

public View getPrimaryItem() {return mCurrentView;
}

保存相片到手机相册

public void saveImageToGallery(Context context, Bitmap bmp) {// 首先保存图片File picDir;if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {picDir = new File(context.getExternalCacheDir(), "image");} else {picDir = new File(context.getCacheDir(), "image");}if (!picDir.exists()) {picDir.mkdir();}String fileName = System.currentTimeMillis() + ".jpg";File file = new File(picDir, fileName);try {FileOutputStream fos = new FileOutputStream(file);bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);fos.flush();fos.close();Toast.makeText(context, "图片已生成", Toast.LENGTH_SHORT).show();// 其次把文件插入到系统图库MediaStore.Images.Media.insertImage(context.getContentResolver(),file.getAbsolutePath(), fileName, null);// 最后通知图库更新Uri uri;/*if (Build.VERSION.SDK_INT >= 24) { //7.0获取uri的方式变了String authority = getPackageName() + ".fileprovider"; //授权只要保证是唯一字符串即可,需保证和清单文件注册时的provider的name一致uri= FileProvider.getUriForFile(this, authority, file);} else {uri= Uri.fromFile(file);//7.0之前的方式}*//* uri = Uri.parse("file://" + file.getAbsolutePath());context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));//据说部分手机不行*/final MediaScannerConnection[] mScanner = {null};MediaScannerConnection mediaConnection = new MediaScannerConnection(context, new MediaScannerConnection.MediaScannerConnectionClient() {@Overridepublic void onMediaScannerConnected() {mScanner[0].scanFile(file.getAbsolutePath(), file.getName());}@Overridepublic void onScanCompleted(String path, Uri uri) {mScanner[0].disconnect();mScanner[0] = null;ToastUtils.showShort("图片已保存至相册");}});mediaConnection.connect();mScanner[0] = mediaConnection;} catch (Exception e) {e.printStackTrace();Toast.makeText(context, "图片生成失败", Toast.LENGTH_SHORT).show();}}

递归删除目录及其所有子目录内容

public static boolean deleteAllFile(File sourceFile) {if (!sourceFile.exists()) {return false;}File[] files = sourceFile.listFiles();if (files != null && files.length > 0) {for (int i = 0; i < files.length; i++) {File file = files[i];if (file.isFile()) {file.delete();} else if (file.isDirectory()) {deleteAllFile(file);}if (i == (files.length - 1)) {sourceFile.delete();}}} else {sourceFile.delete();}return true;
}

getLocationInWindow 和 getLocationOnScreen区别


以控件C为例:
getLocationInWindow是以B为原点的C的左上角坐标
getLocationOnScreen是以A为原点的C的左上角坐标

使用方式:

int[] location = new int[2];
v.getLocationOnScreen(location);
int x = location [0];//获取x坐标
int y = location [1];//获取y坐标

getWindowVisibleDisplayFrame的用法

getWindowVisibleDisplayFrame()是View类下的一个方法,用来获取当前窗口可视区域的大小。该方法原型为:

public void getWindowVisibleDisplayFrame(Rect outRect);

outRect中保存了可视区域的范围,如left, top, right, bottom。

该方法使用注意事项:

  1. 调用该方法的view对象必须在有效的window中,比如activity,fragment或者dialog的layout中。类似new TextView(context).getWindowVisibleDisplayFrame(rect)无法得到正确的结果。

  2. 该方法必须在view已经attach到window时调用才能得到期望的正确结果。比如我们可以在Activity、Fragment和Dialog的onWindowFocusChanged()方法中执行,在view的onAttachedToWindow()中可能无法获得正确结果

  3. outRect所表示的只是窗体可见范围,其会受到系统状态栏,虚拟键盘和导航栏的影响,状态栏主要影响outRect的top值,虚拟键盘和导航栏会影响outRect的bottom值


DecorView的介绍

详情


保留2位小数

public class Test {public static void main(String[] args) {double d = 756.2345566;//方法一:最简便的方法,调用DecimalFormat类DecimalFormat df = new DecimalFormat("0.00");//0和#都是占位符,不同点是0在小数为不够的时候会用0补,#的话不会, 正数位无影响.System.out.println(df.format(d));//方法二:直接通过String类的format函数实现System.out.println(String.format("%.2f", d));//方法三:通过BigDecimal类实现BigDecimal bg = new BigDecimal(d);double d3 = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();System.out.println(d3);//方法四:通过NumberFormat类实现NumberFormat nf = NumberFormat.getNumberInstance();nf.setMaximumFractionDigits(2);System.out.println(nf.format(d));}
}

软键盘弹出自动时滑动到指定View的下方显示

/*** 处理软键盘弹出界面滚动* Created by mChenys on 2019/12/20.*/public class SoftInputUtil {// 控制是否移动布局。比如只有密码输入框获取到焦点时才执行。public static boolean flag = true;public interface Callback {//软件盘状态变化回调void onKeyboardStateChange(boolean isOpen);//页面滚动前回调int beforeScroll();}/*** @param act          activiry用于获取底部导航栏高度。* @param root         最外层布局,需要调整的布局* @param scrollToView 被键盘遮挡的scrollToView,滚动root,使scrollToView在root可视区域的底部*/public static void controlKeyboardLayout(Context act, final View root, final View scrollToView, Callback callback) {final int navigationBarHeight = getNavigationBarHeight(act);root.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {Rect rect = new Rect();//获取root在窗体的可视区域root.getWindowVisibleDisplayFrame(rect);//根View的高度int decorViewHeight = root.getRootView().getHeight();//获取root在窗体的不可视区域高度(被其他View遮挡的区域高度)int rootInvisibleHeight = decorViewHeight - rect.bottom;//若不可视区域高度大于100,则键盘显示if (rootInvisibleHeight > navigationBarHeight && flag) {int[] location = new int[2];//获取scrollToView在窗体的坐标scrollToView.getLocationInWindow(location);//计算root滚动高度,使scrollToView在可见区域int srollHeight = (location[1] + scrollToView.getHeight()) - rect.bottom;if (root.getScrollY() != 0) {// 如果已经滚动,要根据上次滚动,重新计算位置。srollHeight += root.getScrollY();}int dy = 0; //偏移量,可以调节滚动距离if (null != callback) dy = callback.beforeScroll();root.scrollTo(0, srollHeight + dy);if (null != callback) callback.onKeyboardStateChange(true);} else {//键盘隐藏root.scrollTo(0, 0);if (null != callback) callback.onKeyboardStateChange(false);}}});}/*** 获取底部导航栏高度** @param act* @return*/private static int getNavigationBarHeight(Context act) {Resources resources = act.getResources();int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");int height = resources.getDimensionPixelSize(resourceId);Log.v("dbw", "Navi height:" + height);return height;}
}

exoplayer获取当前帧画面

这个播放器支持surfaceView和TextureView 两种视图模式的展示,
对于surfaceView好像做不到转换成bitmap 但是TextureView 就很简单了.

PlayerView playerView = (PlayerView) LayoutInflater.from(application).inflate(R.layout.layout_exo_player_view, null, false);
TextureView textureView = (TextureView)playerView.getVideoSurfaceView();
Bitmap bitmap = textureView.getBitmap();

通过adb shell命令查看当前与用户交互的activity

方法一:

adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'


其中TaskRecord即为查询到的记录。其中 com.android.calendar为包名,.AllInOneActivity为对应的Activity名称。

方法二:

adb shell dumpsys activity | grep -i run

查询结果为:

如果在Windows下使用时,则需先通过adb shell进入到shell里,然后再执行命令, 例如方法二可以这样操作:


Android SpannableString 自定义圆角背景

参考


RecyclerView获取滚动距离


import android.content.Context;
import android.view.View;import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;import java.util.HashMap;
import java.util.Map;/*** 自定义LayoutManager */
public class OffsetLinearLayoutManager extends LinearLayoutManager {public OffsetLinearLayoutManager(Context context) {super(context);}private Map<Integer, Integer> heightMap = new HashMap<>();/*** 统计列表已展示过的item的高度,在每次布局完成的时候,用一个map记录positon位置item对应的view的高度*/@Overridepublic void onLayoutCompleted(RecyclerView.State state) {super.onLayoutCompleted(state);int count = getChildCount();for (int i = 0; i < count; i++) {View view = getChildAt(i);heightMap.put(i, view.getHeight());}}/*** 找到当前屏幕第一个可见item的position,通过heightMap循环累加0到positon的item高度,* 再加上第一个可见item不可见部分高度。最终得到整个列表的滑动偏移** @param state* @return*/@Overridepublic int computeVerticalScrollOffset(RecyclerView.State state) {if (getChildCount() == 0) {return 0;}try {int firstVisiablePosition = findFirstVisibleItemPosition();View firstVisiableView = findViewByPosition(firstVisiablePosition);int offsetY = -(int) (firstVisiableView.getY());for (int i = 0; i < firstVisiablePosition; i++) {offsetY += heightMap.get(i) == null ? 0 : heightMap.get(i);}return offsetY;} catch (Exception e) {return 0;}}
}

解析动态style的属性

public static void applyStyle(int styleId){int[] ints = {R.attr.cust_color1,R.attr.cust_color2,R.attr.cust_color3,R.attr.cust_drawable4};// 创建themeResources.Theme theme = getResources().newTheme();// 使用指定的styleIdtheme.applyStyle(styleId, true);// 动态解析属性TypedArray typedArray = theme.obtainStyledAttributes(ints);int color1= typedArray.getColor(0, 0);int color2= typedArray.getColor(1, 0);int color3= typedArray.getColor(2, 0);Drawableb drawable4 = typedArray.getDrawable(3, 0);}

然后使用时在res/values/styles.xml中定义要解析的style即可

<?xml version="1.0" encoding="utf-8"?><style name="my_style1" ><item name="cust_color1">@color/color_1</item><item name="cust_color2">@color/color_2</item><item name="cust_color3">@color/color_3</item><item name="cust_drawable4">@drawable/ic_background</item></style>

并且在res/values/attrs.xml中自定义属性名

<?xml version="1.0" encoding="utf-8"?>
<resources><attr name="cust_color1" format="reference"></attr><attr name="cust_color2" format="color"></attr><attr name="cust_color3" format="color"></attr><attr name="cust_drawable4" format="reference"></attr>
<resources>

跳应用市场和搜索页

 //跳转应用商店private fun intentAppStore(packageName: String, markPackageName: String) {val uri = Uri.parse("market://details?id=$packageName")val intent = Intent(Intent.ACTION_VIEW, uri)intent.setPackage(markPackageName)intent.flags = Intent.FLAG_ACTIVITY_NEW_TASKmContext.startActivity(intent)}// 跳去应用市场搜索appprivate fun intentSearchApp(appName: String) {val intent = Intent(Intent.ACTION_VIEW)intent.data = Uri.parse("market://search?q=pub:+" + appName)intent.flags = Intent.FLAG_ACTIVITY_NEW_TASKmContext.startActivity(intent)}

将含有其他aar包的module打包成新的aar

如果直接将其它的aar包放到module的libs就开始打包,虽然也可以打成功,但是生成aar包放到项目中使用的时候如果访问该aar所依赖的其它aa的类就会报找不到目标类的异常,解决办法就是还得在项目中依赖这个aar所依赖的其他aar包,显然这并不是我们所期望的.

这里介绍正确的解决方案是使用fat-aar-android插件.

fat-aar可以解决aar嵌套aar打包的需求场景.

使用步骤如下:
1.编辑主工程的build.gradle文件

// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: this.rootProject.file('project.gradle')
buildscript {ext.kotlin_version = '1.3.72'repositories {google()jcenter()maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }maven { url "https://jitpack.io" }//1.添加fat插件仓库地址maven { url 'http://plugins.gradle.org/m2/' }}dependencies {classpath "com.android.tools.build:gradle:3.6.0"classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"//2.添加插件classpath "com.kezong:fat-aar:1.3.3"}
}allprojects {repositories {//3.配置flatDirflatDir { dirs 'libs' }google()jcenter()maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }maven { url "https://jitpack.io" }//4.添加fat插件仓库地址maven { url 'http://plugins.gradle.org/m2/' }}}task clean(type: Delete) {delete rootProject.buildDir
}

2.编辑需要打包生成aar的module工程的build.gradle文件

apply plugin: 'com.android.library'
apply plugin: 'com.kezong.fat-aar'  //1.引用fat插件android {...
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])// 通过embed来嵌套其他aar包,这里以阿里百川的aar为例,aar包是放在module的libs目录的embed(name: 'alibabauth_core-2.0.0.11', ext: 'aar')embed(name: 'alibabauth_ext-2.0.0.11', ext: 'aar')embed(name: 'alibabauth_ui-2.0.0.11', ext: 'aar')embed(name: 'alibc_link_partner-4.1.15', ext: 'aar')embed(name: 'AlibcTradeBiz-4.0.0.16', ext: 'aar')embed(name: 'AlibcTradeCommon-4.0.0.16', ext: 'aar')embed(name: 'avmpaar3-5.4.36', ext: 'aar')embed(name: 'nb_trade-4.0.0.16', ext: 'aar')embed(name: 'securitybodyaar3-5.4.99', ext: 'aar')embed(name: 'securityguardaar3-5.4.171', ext: 'aar')embed(name: 'sgmiddletieraar3-5.4.9', ext: 'aar')
}

确保module的libs存在这些aar包

ps: 如果遇到依赖的外部sdk(aar)相互之间出现资源重复冲突,例如app_name冲突可以在主工程的gradle.properties中加入:

android.disableResourceValidation=true

通过RecyclerView和item获取对应的ViewHolder

  //先获取itemView item= mRecyclerView.getChildAt(0);//或者LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();item= layoutManager.findViewByPosition(0);//再通过child获取ViewHolderRecyclerView.ViewHolder viewHolder = mRecyclerView.findContainingViewHolder(item);

通过RecyclerView的findChildViewUnder(x,y)返回指定位置的childView

方法参数是 (float x, float y),作用是查找指定坐标点 (x, y) 落于 RecyclerView 的哪个子 View 上面,这里的坐标点是以 RecyclerView 控件作为坐标轴,并不是以屏幕左上角作为坐标原点。方法的源码如下:

public View findChildViewUnder(float x, float y) {final int count = mChildHelper.getChildCount();for (int i = count - 1; i >= 0; i--) {final View child = mChildHelper.getChildAt(i);final float translationX = child.getTranslationX();final float translationY = child.getTranslationY();// 其实就是判断传入的x,y是否落在某个子View的范围内,是的话就返回if (x >= child.getLeft() + translationX&& x <= child.getRight() + translationX&& y >= child.getTop() + translationY&& y <= child.getBottom() + translationY) {return child;}}return null;
}

这个ChildHelper类,它会协助获取RecyclerView中的childVIew,并提供忽略隐藏Children的功能,也就是说,调它的getChildAt只会在当前显示的Children中去查找,如果想查HiddenChildren,那么可以调getUnfilteredChildAt。


关闭RecyclerView 的测量、布局操作

调用setLayoutFrozen()该方法传入true,其实等效于关闭了 ReyclerView 的刷新,不管数据源发生了何种变化,不管用户滑动了多长距离,都不会去刷新界面,看起来就像是不响应一样,但等到再次调用该方法参数传入 false 后,就会立马去根据变化后的数据源来刷新界面了。

使用场景还是有的,假如有些场景暂时不想让 RecyclerView 去刷新,比如此时有其他动画效果正在执行中,RecyclerView 刷新多少会有些耗时,万一导致了当前动画的卡顿,那么体验就不好了。所以,这个时候可以暂时将 ReyclerView 的刷新关闭掉,但后面记得要重新开启。
ps:新版本的RecyclerView该方法已标注为过时方法,可以使用suppressLayout代替.

通过RecyclerView的findContainingItemView(view)得到包含指定View的ItemView

该方法参数是 (View view),作用正如命名上的理解,查找含有指定 View 的 ItemView,而 ItemView 是指 RecyclerView 的直接子 View
通常,RecyclerView 的 Item 布局都不会简单到直接就是一个具体的 TextView,往往都挺复杂的。


全屏并让视图延伸到刘海区域

通常启动页都会设置为全屏展示,但是遇到刘海屏的时候,会发现刘海区域是黑的,如下图所示

解决办法如下:

<style name="AppThemeFull" parent="Theme.AppCompat.Light.NoActionBar"><item name="android:statusBarColor">@android:color/transparent</item><item name="android:navigationBarColor">@android:color/transparent</item><item name="windowActionBar">false</item><item name="android:windowFullscreen">true</item><item name="windowNoTitle">true</item><!--允许页面延伸到刘海区域,需要在values-v28中定义--><item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item></style>

本人华为荣耀20上试过有效,小米和vivo没试过,如果不行的话,可以试试如下方式:

 <application<!--vivo刘海屏适配--><meta-dataandroid:name="android.max_aspect"android:value="2.4" /><!--小米刘海屏适配--><meta-dataandroid:name="notch.config"android:value="portrait|landscape" /></application>

SmartRefreshLayout自定义刷新头

只需要继承SimpleComponent和实现RefreshHeader接口即可,然后根据需求实现对应的文案或者动画效果


import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;import androidx.annotation.NonNull;import com.scwang.smart.refresh.layout.api.RefreshHeader;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.constant.RefreshState;
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
import com.scwang.smart.refresh.layout.simple.SimpleComponent;/*** @Author: mChenys* @Date: 2021/2/20* @Description:自定义刷新头*/
public class DemoRefreshHeader extends SimpleComponent implements RefreshHeader {private TextView mTitleText;private ImageView mLoadingView;private ObjectAnimator mObjectAnimator;public DemoRefreshHeader(Context context) {super(context, null, 0);View view = View.inflate(getContext(), R.layout.layout_header_demo, this);mTitleText = view.findViewById(R.id.tv_title);mLoadingView = view.findViewById(R.id.iv_loading);mObjectAnimator = ObjectAnimator.ofFloat(mLoadingView, "rotation", 0f, 360f);mObjectAnimator.setDuration(100L);mObjectAnimator.setRepeatMode(ValueAnimator.RESTART);mObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);}/*** 头部在下拉时的变化方式** @return Translate, //平行移动        特点: HeaderView高度不会改变,* Scale, //拉伸形变            特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件* FixedBehind, //固定在背后    特点:HeaderView高度不会改变,* FixedFront, //固定在前面     特点:HeaderView高度不会改变,* MatchLayout//填满布局        特点:HeaderView高度不会改变,尺寸充满 RefreshLayout*/@NonNull@Overridepublic SpinnerStyle getSpinnerStyle() {return SpinnerStyle.Translate;}/*** 松手且达到刷新条件时回调** @param refreshLayout* @param height* @param maxDragHeight*/@Overridepublic void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {onStartAnimator(refreshLayout, height, maxDragHeight);}/*** 动画执行回调** @param refreshLayout* @param height* @param maxDragHeight*/@Overridepublic void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {if (mLoadingView.getVisibility() != View.VISIBLE) {mLoadingView.setVisibility(View.VISIBLE);mObjectAnimator.start();}}/*** 刷新完成时回调** @param refreshLayout* @param success* @return*/@Overridepublic int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {mObjectAnimator.cancel();mLoadingView.setVisibility(View.GONE);if (success) {mTitleText.setText("刷新完成");} else {mTitleText.setText("刷新失败");}return 500;//延迟500毫秒之后再弹回}/*** 持续下拉持续回调** @param isDragging* @param percent* @param offset* @param height* @param maxDragHeight*/@Overridepublic void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {// 按下拉百分比旋转mLoadingView.setRotation(percent * 360);}/*** 状态改变时回调** @param refreshLayout* @param oldState* @param newState*/@Overridepublic void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {switch (newState) {case None:case PullDownToRefresh:mTitleText.setText("下拉刷新");mLoadingView.setVisibility(View.VISIBLE);break;case Refreshing:case RefreshReleased:mTitleText.setText("正在刷新...");mLoadingView.setVisibility(View.GONE);break;case ReleaseToRefresh:mTitleText.setText("释放立即刷新");break;}}
}

上面的效果就是下拉过程中文案会由"下拉刷新"->“释放立即刷新”->“释放立即刷新”->“刷新完成”, 而动画效果就是有一个加载圈在持续下拉的过程会根据下拉百分比来旋转,当变成"正在刷新…"的时候会播放旋转动画,当刷新完成后会取消动画

使用方式也很简单,全局使用可以在初始的时候配置如下:

 //设置全局的Header构建器SmartRefreshLayout.setDefaultRefreshHeaderCreator(new DefaultRefreshHeaderCreator() {@NonNull@Overridepublic RefreshHeader createRefreshHeader(@NonNull Context context, @NonNull RefreshLayout layout) {// 返回上面自定义的刷新头return new DemoRefreshHeader(context);}});

将图片绘制到指定大小的View上面

// 重写View的onSizeChanged获取View的宽高override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {mWidth = wmHeight = h}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)drawBg(canvas)}// 绘制背景private fun drawBg(canvas: Canvas?) {// 1.获取资源图片val srcBitmap = BitmapFactory.decodeResource(resources, R.drawable.img_bg)// 2.创建空的bitmap ,宽高要等于View的宽高val bgBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888)// 3.将资源图片绘制到空的bitmap中val bgCanvas = Canvas(bgBitmap)bgCanvas.drawBitmap(srcBitmap, null, Rect(0, 0, mWidth, mHeight), mPaintBg)// 4.将bgBitmap绘制到View上canvas?.drawBitmap(bgBitmap , null, Rect(0, 0, mWidth, mHeight), mPaintBg)}

获取Bitmap指定区域和大小的内容

 val srcBitmap = BitmapFactory.decodeResource(resources, R.drawable.img_bg)val newBitmap = Bitmap.createBitmap(srcBitmap ,  // 原图LINE_X.toInt(), // x坐标LINE_Y.toInt(), // y坐标CARD_SIZE, // 需要截取的大小CARD_SIZE // 需要截取的大小)

IdleHandler (闲时机制)

IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会在回调之后移除它,返回true就会在下次message处理完了的时候继续回调。

registerForActivityResult的使用

registerForActivityResult()是startActivityForResult()的替代,简化了数据回调的写法
参考


谷歌浏览器调试移动端h5页面

1.安卓手机打开开发者模式连接上电脑
2.在谷歌浏览器中访问地址:

chrome://inspect/#devices

3.刷新,显示连接上的设备以及打开的网页,点击inspect就可以查看手机网页上的dom元素,在电脑上调试h5页面。

transitive和changing的含义

compile('com.crashlytics.sdk.android:answers:1.3.10@aar') {transitive = true;
}

简单地说, 由于该语句使用 @aar notation, 所以 gradle 只会下载这一个 aar 文件, 而不会顺带着下载这个 aar 所需要的依赖文件. 所以需要 transitive 让依赖能够自动被下载. 一般而言, 去掉 @aar 以及 { transitive = true } 不会有任何问题.

compile("com.tencent.tinker:tinker-android-lib:1.7.6") { changing = true
}

这个字段的含义是: 是否自动更新该版本的依赖文件, 但是! 如果像本例中指定了版本, 则只会更新相同版本. 默认是 24 小时检查该依赖. 当然也可以指定检查更新的周期.


gradle 每次运行都会下载依赖的解决办法

在执行build、compile等任务时会解析项目配置的依赖并按照配置的仓库去搜寻下载这些依赖。默认情况下,Gradle会依照Gradle缓存->你配置的仓库的顺序依次搜寻这些依赖,并且一旦找到就会停止搜索。如果想要忽略本地缓存每次都进行远程检索可以通过在执行命令时添加–refresh-dependencies参数来强制刷新依赖

gradle build --refresh-dependencies

当远程仓库上传了相同版本依赖时,有时需要为缓存指定一个时效去检查远程仓库的依赖笨版本,Gradle提供了

cacheChangingModulesFor(int, java.util.concurrent.TimeUnit),
cacheDynamicVersionsFor(int, java.util.concurrent.TimeUnit)两个方法来设置缓存的时效
configurations.all {//每隔24小时检查远程依赖是否存在更新resolutionStrategy.cacheChangingModulesFor 24, 'hours'//每隔10分钟..//resolutionStrategy.cacheChangingModulesFor 10, 'minutes'// 采用动态版本声明的依赖缓存10分钟,也就是changing: trueresolutionStrategy.cacheDynamicVersionsFor 10*60, 'seconds'
}dependencies {// 添加changing: truecompile group: "group", name: "module", version: "1.1-SNAPSHOT", changing: true//简写方式//compile('group:module:1.1-SNAPSHOT') { changing = true }
}

Gradle在按照配置的仓库去搜寻下载依赖时,下载的依赖默认会缓存到USER_HOME/.gradle/caches目录下,当然也可以手工修改这个位置。

Gradle还提供了一种离线模式,可以让你构建时总是采用缓存的内容而无需去联网检查,如果你并未采用动态版本特性且可以确保项目中依赖的版本都已经缓存到了本地,这无疑是提高构建速度的一个好选择。开启离线模式只需要在执行命令时候添加–offline参数即可。当然,采用这种模式的也是有代价的,如果缓存中搜寻不到所需依赖会导致构建失败。

gradle build --offline

同时兼容高本gradle和低版本gradle插件版本

在项目根gradle文件中修改如下,同时兼容gradle3.5.3和2.3.3插件版本

buildscript {repositories {mavenLocal()jcenter()google()}dependencies {if (project.hasProperty('GRADLE_3') && GRADLE_3.equalsIgnoreCase('TRUE')) {classpath 'com.android.tools.build:gradle:3.5.3' //gradle3.0之后版本} else {classpath 'com.android.tools.build:gradle:2.3.3' //gradle3.0之前版本}...}def is_gradle_3() {return hasProperty('GRADLE_3') && GRADLE_3.equalsIgnoreCase('TRUE')}
}

然后在app的gradle文件添加依赖的时候也要区分下,如下以tinker依赖添加为例

dependencies {if (is_gradle_3()) {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'junit:junit:4.12'implementation "androidx.appcompat:appcompat:1.1.0"api("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }implementation("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true }annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }compileOnly("com.tencent.tinker:tinker-android-anno-support:${TINKER_VERSION}") { changing = true }implementation "androidx.multidex:multidex:2.0.1"} else {compile fileTree(dir: 'libs', include: ['*.jar'])testCompile 'junit:junit:4.12'compile "androidx.appcompat:appcompat:1.1.0"compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }provided("com.tencent.tinker:tinker-android-anno-support:${TINKER_VERSION}") { changing = true }compile "androidx.multidex:multidex:2.0.1"}
}

multiDexKeepFile属性 和multiDexKeepProguard属性的区别

这2者的作用都是为了解决multiDex拆分包的过程中会出现在启动时需要用到的某个类没有在主dex中找到而产生java.lang.NoClassDefFoundError的崩溃问题.

1)使用multiDexKeepFile
您在 multiDexKeepFile 中指定的文件应该每行包含一个类,并且类采用 com/example/MyClass.class 格式。例如,您可以创建一个名为 multidex-config.txt 的文件,如下所示:

com/example/MyClass.class
com/example/MyOtherClass.class

然后,您可以针对构建类型声明该文件,如下所示:

android {buildTypes {release {multiDexKeepFile file('multidex-config.txt')...}}
}

请注意,Gradle 会读取相对于 build.gradle 文件的路径,因此如果 multidex-config.txt 与 build.gradle 文件在同一目录中,以上示例将有效。

2)使用multiDexKeepProguard
multiDexKeepProguard 文件使用与 Proguard 相同的格式,并且支持全部 Proguard 语法。如需详细了解 Proguard 格式和语法,请参阅 Proguard 手册中的 Keep 选项一节。

您在 multiDexKeepProguard 中指定的文件应该在任何有效的 ProGuard 语法中包含 -keep 选项。例如 -keep com.example.MyClass.class。您可以创建一个名为 multidex-config.pro 的文件,如下所示:

-keep class com.example.MyClass
-keep class com.example.MyClassToo

如果您要指定软件包中的所有类,文件将如下所示:

-keep class com.example.** { *; } // All classes in the com.example package

然后,您可以针对构建类型声明该文件,如下所示:

android {buildTypes {release {multiDexKeepProguard file('multidex-config.pro')...}}
}

更多使用说明可以参考官方文档


build.gradle配置中packagingOptions{}选项说明

参考


gradle配置资源路径

def isModule = trueandroid {...defaultConfig {...}...sourceSets {main {// 设置jinLibs目录为src/main/libsjniLibs.srcDirs = ['libs']// 设置资源文件目录res.srcDirs = ['src/main/res','src/main/res-debug',]if (!isModule) {// 组件化模式下,独立运行的清单文件路径manifest.srcFile 'src/main/debug/AndroidManifest.xml'} else {// 集成化模式,作为app的依赖module运行的清单文件路径manifest.srcFile 'src/main/AndroidManifest.xml'java {// 集成化模式下需要排除的目录,可以用通配符标示exclude "**/debug/**"}}}}

如下图所示

Android library打包生成jar包

当我们的Android library是纯代码的情况,如果你只想生成jar包而不想生成aar包,那么可以使用gradle的task来生成

//打包生成jar包
task makeJar(type: Jar) {//指定生成的jar名baseName 'mylibrary'//从哪里打包class文件from(new File(buildDir,"intermediates/javac/debug/classes/com/sample/mylibrary"))//打包到jar后的目录结构into('com/sample/mylibrary')//去掉不需要打包的目录和文件exclude('BuildConfig.class', 'R.class')//去掉R$开头的文件exclude { it.name.startsWith('R$'); }
}

执行完task后就会在build/libs目录下生成对应的jar包了


cmd命令行中logcat输出日志中文乱码

在命令行使用adb logcat命令直接输出日志中文内容显示乱码,原因是中文系统中cmd命令行窗口默认的编码是GBK,而LogCat打印的日志是UTF-8编码,所以adb logcat命令输出的中文内容显示乱码。

修改cmd命令行窗口字符编码即可解决logcat日志中文显示乱码问题:

1.cmd命令行窗口字符编码切换为UTF-8,命令行中执行:chcp 65001

2.修改cmd窗口字体属性,在命令行标题栏上点击右键,选择”属性”->”字体”,将字体修改为”Lucida Console”,点击确定后生效。

修改后logcat -f log.txt方式输出日志到文本文件的乱码问题同样可以解决。

部分字符编码对应代码:
65001——UTF-8
936——简体中文
950——繁体中文
437——美国/加拿大英语
932——日文
949——韩文
866——俄文

cmd窗口字符编码切换回中文:chcp 936


从未安装的APK中获取默认启动的Activity

参考


使用aapt2 修改apk打包的资源id

参考


使用AS查看framework源码

Android有两种类型的API是不能经由SDK访问的。

第一种是位于com.android.internal包中的API。我将称之为internal API。
第二种API类型是一系列被标记为@hide属性的类和方法。
Internal和hidden API的编译时 vs 运行时
当你使用Android SDK进行开发的时候,你引用了一个非常重要的jar文件——android.jar。它位于Android SDK平台的文件夹中(SDK_DIR/platforms/platform-X/android.jar)。这个android.jar移掉了com.android.internal包中所有的类,也移掉了部分标记有@hide的类,枚举,字段和方法。

每个模拟器或真机在运行时都会有一个等同android.jar的东西,叫做framework.jar,它和android.jar等同,而其未移掉internal API和hidden API。当你在设备上启动应用程序时,它将加载framework.jar。

但这样对开发者来说,并不能友好地访问,因此,我将向大家展示不通过反射如何使用这些API。

解决办法如下:
在完整版android.jar下载对应版本的android.jar,然后替换 ${Android Sdk}/platforms/andorid-api/ 下的android.jar即可

BottomNavigationView 底部导航栏加入 红点数字提示

参考


TabLayout去掉点击的默认背景色

 <com.google.android.material.tabs.TabLayoutandroid:id="@+id/bottom_navigation"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/white"android:paddingTop="5dp"android:paddingBottom="5dp"app:tabBackground="@android:color/transparent"app:tabIndicatorColor="@android:color/transparent"app:tabRippleColor="@android:color/transparent" />

主要就是app:tabRippleColor的属性


Android9,10反射限制问题分析以及解决

public final class ReflectionLimit {private static Object sVMRuntime;private static Method setHiddenApiExemptions;static {if ( Build.VERSION.SDK_INT >= 29 ) {try {Method forName = Class.class.getDeclaredMethod("forName", String.class);Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});setHiddenApiExemptions.setAccessible(true);sVMRuntime = getRuntime.invoke(null);} catch (Exception e) {e.printStackTrace();}}}//消除限制public static boolean clearLimit() {if (sVMRuntime == null || setHiddenApiExemptions == null) {return false;}try {setHiddenApiExemptions.invoke(sVMRuntime, new Object[]{new String[]{"L"}});return true;} catch (Exception e) {e.printStackTrace();}return false;}
}

参考


Gradle强制刷新单模块依赖

参考


android:clipToPadding的使用

当父布局设置了padding,而又想子布局在滑动的时候可以无视padding,也就是在padding区域绘制(换句话说就是允许子View滑动的区域包含父容器的padding),此时就需要设置clipToPadding = false,默认是true。
具体可以查看ViewGroup的源码:

protected void dispatchDraw(Canvas canvas) {//查询是否设置了clipToPaddingfinal boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;if (clipToPadding) { // 默认是trueclipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);//对canvas设置裁减区域canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom);}}

应用场景参考


RecyclerView嵌套RecyclerView的内存复用

总而言之 如果RecycledView的adapter是一样的话可以考虑共享一个对象池。
比如说: RecycledView嵌套RecycledView,里面的RecycledView大部分都adapter都一样。
注意 如果 LayoutManager是LinearLayoutManager或其子类(如GridLayoutManager) 需要手动开启这个特性:layout.setRecycleChildrenOnDetach(true)

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {// 创建复用池RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();...@Override
public void onCreateViewHolder(ViewGroup parent, int viewType) {// 嵌套的子RecyclerViewRecyclerView view = new RecyclerView(inflater.getContext());LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL);// 布局主动开启内存复用innerLLM.setRecycleChildrenOnDetach(true);innerRv.setLayoutManager(innerLLM);// 设置复用池innerRv.setRecycledViewPool(mSharedPool);return new OuterAdapter.ViewHolder(innerRv);}...

更新于2021/06/21


indexOf(String str, int fromIndex)和lastIndexOf(String str, int fromIndex)区别

indexOf(String str, int fromIndex)//从当前 fromIndex位置往从左往右查找最前一个
lastIndexOf(String str, int fromIndex)//从当前 fromIndex位置往从右往左查找最后一个

获取TextView指定span的起点和终点的x坐标

   // 获取span的起始位置x坐标private Pair<Integer, Integer> getSpanRangeX(TextView widget, Layout layout, int line,Spannable buffer, Object span) {int spanStart = buffer.getSpanStart(span); // span的起点index,从0开始int spanEnd = buffer.getSpanEnd(span); // span的终点indexint lineStart = layout.getLineStart(line);// 当前行的start indexint lineEnd = layout.getLineEnd(line);// 当前行的end indexString text = widget.getText().subSequence(lineStart, lineEnd).toString(); // 当前行文本int width = (int) widget.getPaint().measureText(text); // 当前行文本宽度int spanStartX = (int) ((spanStart - lineStart) / (text.length() * 1.0f) * width); // span的起点x坐标int spanEndX = (int) ((spanEnd - lineStart) / (text.length() * 1.0f) * width); // span的终点x坐标return new Pair<>(spanStartX, spanEndX);}

至于如何获取TextView中的某一行line和指定的span,可以这样操作:

if (action == MotionEvent.ACTION_UP|| action == MotionEvent.ACTION_DOWN|| action == MotionEvent.ACTION_MOVE|| action == MotionEvent.ACTION_CANCEL) {int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);// 获取点击的span,例如:ClickableSpanXxxSpan[] XxxSpans = buffer.getSpans(off, off, XxxSpan.class); // 这里XxxSpan换成具体的span即可.
}

图片Uri转File

public static File genNewFileFromUri(Context context, Uri uri) {File imgFile = context.getExternalCacheDir();File file = null;if (!imgFile.exists()) {imgFile.mkdir();}try {String mimeType = context.getContentResolver().getType(uri);String format = mimeType.contains("gif") ? ".gif" : ".jpg";file = new File(imgFile.getAbsolutePath(),System.currentTimeMillis() + format);// 使用openInputStream(uri)方法获取字节输入流InputStream fileInputStream = context.getContentResolver().openInputStream(uri);FileOutputStream fileOutputStream = new FileOutputStream(file);byte[] buffer = new byte[1024];int byteRead;while (-1 != (byteRead = fileInputStream.read(buffer))) {fileOutputStream.write(buffer, 0, byteRead);}fileInputStream.close();fileOutputStream.flush();fileOutputStream.close();} catch (Exception e) {e.printStackTrace();}return file;}

toolbar修改返回按键颜色

参考

Kotlin自动findViewById神器-LayoutContainer

参考

RecycleView的layoutManager.setStackFromEnd(true);layoutManager.setReverseLayout(true);

参考

Android 设置TextView自动调整字体大小

参考

Java正则表达式Pattern.quote()方法详解

参考
通常用来构建表情emoji字符的Pattern,例如:

fun test() {val emoji = "[奋斗]"val pattern = Pattern.compile(Pattern.quote(emoji)) // Pattern.quote("[奋斗]") == \Q[奋斗]\Eval spannableString = SpannableString("hello everyone [奋斗]")val matcher = pattern.matcher(spannableString)if(matcher.find()){val imageSpan: ImageSpan? = generateImageSpan(emoji)if (imageSpan != null) {spannableString.setSpan(imageSpan, matcher.start(), matcher.end(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)}}
}

按钮水波纹效果

参考

扫描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的引用是否有效,例如:

@Override
public void show() {Activity activity = UIUtils.scanForActivity(getContext());if (null != activity && !activity.isFinishing())super.show();
}

巧用Z值修改View的层级

参考

给ImageView的图片上色

通过tint属性就可以填充颜色了.

app:tint="@color/color_ffffff_60"

adjustViewBounds使ImageView和图片比例保持一致

adjustViewBounds只有在ImageView一边固定,一边为wrap_content的时候才有意义。设置为true的时候,可以让ImageView的比例和原始图片一样,以达到让图片充满的ImageView的效果。

Android 更换字体和改变字重

参考

TextView设置高亮点击

效果图:

/*** 显示重试*/@JvmOverloadsfun showRetryView(tipText: CharSequence? = R.string.没有匹配结果.asString(), @DrawableRes errorIcon: Int? = R.drawable.common_ic_net_error,retryText: CharSequence? = R.string.重试_4150.asString(),callback: () -> Unit) {tvDefaultTip.setVisible()ivDefaultIcon.setVisible()setDefaultIcon(errorIcon)retryCallback = callback// 1.必须要给TextView设置movementMethod,否则点击无效tvDefaultTip.movementMethod = LinkMovementMethod.getInstance();// 2.设置SpannableStringBuilder或者SpannableString都可以tvDefaultTip.text = SpannableStringBuilder("$tipText$retryText").apply {setSpan(MClickableSpan(), tipText?.length ?: 0, length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)}setVisible()}// 可点击的spaninner class MClickableSpan: ClickableSpan(){override fun updateDrawState(ds: TextPaint) {super.updateDrawState(ds)//设置点击文本的颜色ds.color = R.color.primary_100.asColor()//去除点击文本的默认的下划线ds.isUnderlineText = false}override fun onClick(widget: View) {//去除点击后字体出现的背景色(widget as? TextView)?.highlightColor = Color.TRANSPARENT// 回调点击事件retryCallback?.invoke()}}

巧妙利用rotationY属性处理阿语和非阿语环境下返回箭头的方向

下图1是非阿语

下图2是阿语

解决办法就是给这个箭头使用rotationY属性

 android:rotationY="@integer/rtl_rotation"

rtl_rotation的值定义在2个不同环境的文件内

其中values/integer.xml定义如下

<?xml version="1.0" encoding="utf-8"?>
<resources><integer name="rtl_rotation">0</integer>
</resources>

values-ldrtl/integer.xml定义如下, 其实就是翻转了180°形成镜像

<?xml version="1.0" encoding="utf-8"?>
<resources><integer name="rtl_rotation">180</integer>
</resources>

AppBarLayout折叠后底部有阴影

app:elevation="0dp" // 就解决底部阴影问题了

CollapsingToolbarLayout折叠后背景色

app:contentScrim="@null"

去掉TextView的透明色对emoji表情的影响

/*** 去掉emoji表情的alpha颜色影响* @return*/
fun String.removeEmojiAlphaEffect(): SpannableString {val result = SpannableString(this)val matcher = Pattern.compile("[^\\u0000-\\uFFFF]").matcher(this)while (matcher.find()) {result.setSpan(ForegroundColorSpan(Color.parseColor("#ffffff")),matcher.start(), matcher.end(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE)}return result
}

判断AppBarLayout的折叠状态

import com.google.android.material.appbar.AppBarLayout
import kotlin.math.abs/*** @Author: chenyousheng* @Description: AppBarLayout的状态监听*/
abstract class AppBarLayoutStateChangeListener : AppBarLayout.OnOffsetChangedListener {enum class State {EXPANDED,  //展开COLLAPSED,  //折叠INTERMEDIATE //中间状态}private var mCurrentState = State.INTERMEDIATEoverride fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {mCurrentState = if (verticalOffset == 0) {if (mCurrentState != State.EXPANDED) {onStateChanged(appBarLayout,State.EXPANDED)}State.EXPANDED} else if (abs(verticalOffset) >= appBarLayout.totalScrollRange) {if (mCurrentState != State.COLLAPSED) {onStateChanged(appBarLayout,State.COLLAPSED)}State.COLLAPSED} else {if (mCurrentState != State.INTERMEDIATE) {onStateChanged(appBarLayout,State.INTERMEDIATE)}State.INTERMEDIATE}}abstract fun onStateChanged(appBarLayout: AppBarLayout, state: State)
}

使用方式

 binding.appBarLayout.addOnOffsetChangedListener(object :AppBarLayoutStateChangeListener() {override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {if (state == State.EXPANDED ) {// 展开} else {// 折叠}}})

getChildDrawingOrder实现View层级的倒序排列

class ReverseLinearLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {init {isChildrenDrawingOrderEnabled = true}override fun getChildDrawingOrder(childCount: Int, drawingPosition: Int): Int {if (childCount - 1 >= drawingPosition) {return childCount - 1 - drawingPosition}return drawingPosition}
}

巧用ImageSwitcher实现图片的切换

图像切换器(ImageSwitcher),用于实现类似于Windows操作系统的“Windows照片查看器”中的上一张、下一张切换图片的功能。在使用ImageSwitcher时,必须实现ViewSwitcher.ViewFactory接口,并通过makeView()方法来创建用于显示图片的ImageView。makeView()方法将返回一个显示图片的ImageView。在使用图像切换器时,还有一个方法非常重要,那就是setImageResource方法,该方法用于指定要在ImageSwitcher中显示的图片资源。

通过setInAnimation和setOutAnimation方法可以设置图片切换时的动画效果

public class MainActivity extends Activity{ //声明并初始化一个保存要显示图像id的数组private int[] imageId=new int[]{R.drawable.img01,R.drawable.img02,R.drawable.img03, R.drawable.img04,R.drawable.img05,R.drawable.img06,R.drawable.img07, R.drawable.img08}; private int index=0;//当前显示图像的索引private ImageSwitcher imageSwitcher;//声明一个图像切换器对象@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); imageSwitcher=(ImageSwitcher)findViewById(R.id.imageSwitcher1);//获取图像切换器//设置动画效果imageSwitcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));//设置淡入动画imageSwitcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));//设置淡出动画imageSwitcher.setFactory(new ViewFactory() {//设置View工厂@Overridepublic View makeView() { ImageView imageView=null; imageView=new ImageView(MainActivity.this);//实例化一个ImageView类的对象imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);//设置保持纵横比居中缩放图像imageView.setLayoutParams(new ImageSwitcher.LayoutParams( LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT)); return imageView; } }); imageSwitcher.setImageResource(imageId[index]);//显示默认的图片//“上一张”和“下一张”按钮的控制Button up=(Button)findViewById(R.id.button1); Button down=(Button)findViewById(R.id.button2); up.setOnClickListener(new OnClickListener() { @Overridepublic void onClick(View arg0) { if(index>0){ index--;//图片索引后退一个}else{ index=imageId.length-1;//图片达到最前面一张之后,循环至最后一张} imageSwitcher.setImageResource(imageId[index]);//显示当前图片} }); down.setOnClickListener(new OnClickListener() { @Overridepublic void onClick(View arg0) { if(index<imageId.length-1){ index++;//图片索引前进一个}else{ index=0;//图片达到最后面一张之后,循环至第一张} imageSwitcher.setImageResource(imageId[index]);//显示当前图片} }); }
}

巧用OnBackPressedCallback实现fragment监听Activity的返回键

class HomeFragment : Fragment() {// on back pressed.private val nonInboxOnBackCallback = object : OnBackPressedCallback(false) {override fun handleOnBackPressed() {// 处理返回操作}}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 是否启用监听nonInboxOnBackCallback.isEnabled = true// 设置监听requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,nonInboxOnBackCallback)}
}

Android 关于Fragment重叠问题分析和解决

参考
阻止系统恢复Fragment state,在FragmentActivity保存所有Fragment状态前把Fragment从FragmentManager中移除掉。

   private fun initView() {binding.tabLayout.apply {removeAllTabs()removeOldFragment() // 删除旧的fragmentclearOnTabSelectedListeners()addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {override fun onTabSelected(tab: TabLayout.Tab) {// 设置当前选中位置setCurrentSelectedTab(tab.position)}override fun onTabUnselected(tab: TabLayout.Tab) {}override fun onTabReselected(tab: TabLayout.Tab) {}})mTopNavTab.tabs.onEach { tab ->addTab(newTab().apply { customView = createTabView(tab.index) }, tab.index, tab.index == mCurrPosition)}}}private fun setCurrentSelectedTab(position: Int) {mTopNavTab.getCurrentFragment(position)?.let { currentFragment ->switchFragment(currentFragment)}}// 切换fragmentprivate fun switchFragment(target: Fragment) {val fm = getFragmentManager()fm.beginTransaction().apply {for (childFragment in fm.fragments) {// 对RESUMED状态的Fragment进行操作STARTED操作,会执行onPausesetMaxLifecycle(childFragment, Lifecycle.State.STARTED)hide(childFragment)}val tag = target.javaClass.simpleNameif (!target.isAdded) {add(R.id.flContainer, target, tag) //首次添加}show(target)// 执行target fragment的onResumesetMaxLifecycle(target, Lifecycle.State.RESUMED)commitNowAllowingStateLoss()}}fun onSaveInstanceState(outState: Bundle) {// 移除所有fragment,避免恢复的时候重叠,因为hide()和show()方法对之前保存的fragment会无效removeOldFragment()outState.putInt(KEY_LAST_POSITION, mCurrPosition)}fun onRestoreSaveInstance(outState: Bundle?) {val lastPosition = outState?.getInt(TopNavBlock.KEY_LAST_POSITION, -1) ?: -1if (lastPosition != -1) {mCurrPosition = lastPositioninitView()}}/*** 移除旧的fragment实例*/private fun removeOldFragment() {val fm = getFragmentManager()fm.beginTransaction().apply {// 通过tag查找fm.findFragmentByTag(AFragment::class.java.simpleName)?.let {remove(it)}fm.findFragmentByTag(BFragment::class.java.simpleName)?.let {remove(it)}commitNowAllowingStateLoss()}}

巧用FragmentTransaction的setMaxLifecycle方法触发Fragment的生命周期方法

参考
当我们使用FragmentTransaction的add、hide、show方法进行fragment的添加、隐藏、显示操作的时候并不会执行fragment的onResume和onPause方法,可以通过setMaxLifecycle方法来解决,在ViewPager2里面就使用使用这个来解决.

   // 切换fragmentprivate fun switchFragment(target: Fragment) {val fm = getFragmentManager()fm.beginTransaction().apply {for (childFragment in fm.fragments) {// 对RESUMED状态的Fragment进行操作STARTED操作,会执行onPause方法setMaxLifecycle(childFragment, Lifecycle.State.STARTED)// 隐藏所有已添加的fragmenthide(childFragment)}val tag = target.javaClass.simpleNameif (!target.isAdded) {add(R.id.flContainer, target, tag) //首次添加}// 显示目标fragmentshow(target)// 执行目标fragment的onResume方法setMaxLifecycle(target, Lifecycle.State.RESUMED)commitNowAllowingStateLoss()}}

巧用Layout.getDesiredWidth获取TextView的文字宽度

 val tv = TextView(context)val tp: TextPaint = tv.paintval length: Float = Layout.getDesiredWidth(tv.text.toString(), 0, tv.text.length, tp)

Activity作为弹窗,设置背景透明

<style name="MyTranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar"><!--去掉进退场动画 --><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><item name="android:windowBackground">@android:color/black</item><item name="android:colorBackgroundCacheHint">@null</item></style><!--语音通话邀请对话框使用--><style name="VoiceCallTheme" mce_bogus="1" parent="MyTranslucentTheme"><item name="android:windowIsTranslucent">true</item><item name="android:windowBackground">#90000000</item><item name="android:colorBackgroundCacheHint">@null</item><item name="android:windowNoTitle">true</item><item name="android:colorForeground">#fff</item><item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item></style>

需要注意的是Activity的布局中内容的高度要固定高度,剩余空间将会透明展示

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res-auto"><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/rootLayout"android:layout_width="match_parent"android:layout_height="298dp"android:layout_alignParentBottom="true"android:background="@drawable/call_shape_bg_accept_call_layout">子布局省略...</androidx.constraintlayout.widget.ConstraintLayout></RelativeLayout>

修改BottomSheetDialogFragment的Theme

参考

abstract class BaseBottomSheetDialogFragment : BottomSheetDialogFragment() {//白色模式,会设置导航栏背景色为白灰色,设置导航栏icon为黑色protected open fun isLightMode() : Boolean{return false}//黑色模式,会设置导航栏背景色为黑色,设置导航栏icon为白色protected open fun isDarkMode() : Boolean{return false}override fun getTheme(): Int {if (isLightMode()){return R.style.BottomSheetDialogBg2NavBarLightColor}if (isDarkMode()){return R.style.BottomSheetDialogBg2NavBarDarkColor}return super.getTheme()}
}

styles.xml中定义主题

<style name="BottomSheetDialogBg2NavBarColor" parent="@style/Theme.Design.BottomSheetDialog"><item name="android:windowIsFloating">false</item><item name="android:navigationBarColor">#fafafa</item><item name="android:statusBarColor">@android:color/transparent</item></style><style name="BottomSheetDialogBg2NavBarDarkColor" parent="BottomSheetDialogBg2NavBarColor"><item name="bottomSheetStyle">@style/bottomSheetStyleWrapper</item><item name="android:navigationBarColor">#525252</item></style><style name="BottomSheetDialogBg2NavBarLightColor" parent="BottomSheetDialogBg2NavBarColor"><item name="bottomSheetStyle">@style/bottomSheetStyleWrapper</item><item name="android:navigationBarColor">#fafafa</item></style><style name="bottomSheetStyleWrapper" parent="Widget.Design.BottomSheet.Modal"><item name="android:background">@android:color/transparent</item></style>

判断当前页面是否有弹窗展示

参考

代码中使用点9图

val ninePatchDrawable = BitmapFactory.decodeResource(resources, R.drawable.ic_party_matching_type_bg).run {NinePatchDrawable(resources, this, ninePatchChunk, Rect(), null)
}

给TextView设置背景图作为Span

参考

class BackgroundImageSpan(val textColor: Int,val shadowColor: Int,val hSpace: Int,val vSpace: Int,val drawable: Drawable
) :ReplacementSpan() {override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {return paint.measureText(text, start, end).toInt() + hSpace}override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {val originColor = paint.color// draw backgroundval textWidth = getSize(paint, text, start, end, null)drawable.setBounds(x.toInt(), top, (x + textWidth).toInt(), (bottom + vSpace / 2f).toInt())drawable.draw(canvas)// 如果是绘制纯色的矩形,可以这样处理// canvas.drawRoundRect(x, top.toFloat(), x + textWidth, (bottom + vSpace / 2f), 10f, 10f, Paint().apply { color = Color.RED })// draw texttext?.let {paint.setShadowLayer(5f, 1f, 1f, shadowColor)paint.color = textColorcanvas.drawText(it, start, end, x + hSpace / 2f, y.toFloat(), paint)paint.color = originColorpaint.clearShadowLayer()}}
}

使用示例:

val finalText = "$subModeText${recommendParty.tip}"
val spannableString = SpannableString(finalText).apply {val typeface = ResourcesCompat.getFont(ApplicationContext.getContext(), com.yibasan.squeak.common.R.font.font_bold)setSpan(CustomTypefaceSpan(typeface), 0, subModeText.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)setSpan(AbsoluteSizeSpan(12.dp), 0, subModeText.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)val ninePatchDrawable = BitmapFactory.decodeResource(resources, R.drawable.ic_party_matching_type_bg).run {NinePatchDrawable(resources, this, ninePatchChunk, Rect(), null)}setSpan(BackgroundImageSpan(textColor = R.color.white_100.asColor(),shadowColor = R.color.black_08,hSpace = 8.dp,vSpace = 6.dp,drawable = ninePatchDrawable), 0, subModeText.length,Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
}
mBinding.tvTitle.text = spannableString

Android ViewBinding include怎么玩

参考

Android ViewBinding viewStub怎么玩

viewstub需要先inflate, 然后再bind到对应的viewStub的layout生成binding类中, 例如:

<ViewStubandroid:id="@+id/loadingStub"android:layout_width="0dp"android:layout_height="0dp"android:layout="@layout/live_loading"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/titleBar" />

使用如下:

private val loadingLayout by lazy {binding.loadingStub.inflate()
}private val loadingBinding by lazy {LiveLoadingBinding.bind(loadingLayout)
}

如何获取系统组件的属性

以ImageView的src属性为例

public Mycomponent (Context context, AttributeSet attrs) {super(context, attrs);int src_resource = attrs.getAttributeResourceValue("http://schemas.android.com/apk/res/android", "src", 0);this.setImageBitmap(getDrawable(getResources(),src_resource));
}

如何设置TextView的互动条

android:maxLines="7"
android:scrollbarStyle="outsideInset"
android:scrollbarThumbVertical="@drawable/base_bg_scrollbar"
android:scrollbars="vertical"

控制AppBarLayout子View的滚动

 private fun canScrollBanner(canScroll: Boolean) {val appBarChildAt: View = binding.appBarLayout.getChildAt(0)val appBarParams: AppBarLayout.LayoutParams = appBarChildAt.layoutParams as AppBarLayout.LayoutParamsif (canScroll) {appBarParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSEDappBarChildAt.layoutParams = appBarParams} else {appBarParams.scrollFlags = 0}}

如何在Android P不受限制的使用反射

使用FreeReflection库

设置弹窗的层级

window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
这里设置弹窗的层级,数值越大层级就会越高,也就越会浮在最顶端。用到的是一般的应用层级的时候,window的token,也就是我们构造Dialog时候的Context必须是Activity;如果是系统级的类型参数的时候,必须是全局的ApplicationContext才可以,同时还需要获取到系统级应用的权限。比如上面代码中就是系统级层级,需要获取到系统应用权限。

Android TextView 跑马灯效果

参考

如何代码设置textView的style

(1) textStyle

<TextViewandroid:id="@+id/my_text"android:layout_width="fill_parent"android:layout_height="wrap_content"android:text="Hello World"android:textStyle="bold" />

代码方式:

textview.setTypeface(textview.getTypeface(), Typeface.BOLD);

(2) style

<style name="myStyle" parent="@android:style/Widget.TextView"><item name="android:textSize">@dimen/text_size_huge</item><item name="android:textColor">@color/red</item><item name="android:textStyle">bold</item></style>

只需像往常一样在XML layout/your_layout.xml文件中创建TextView

 <TextViewandroid:id="@+id/tvTitle"style="@style/myStyle"android:layout_width="match_parent"android:layout_height="wrap_content"/>

代码方式设置:

textViewTitle.setTextAppearance(this, R.style.myStyle);

解决FragmentNavigator切换Fragment重建的问题

参考

利用Glide的API获取本地缓存的图片

关键在于onlyRetrieveFromCache(true),如果本地有缓存那么读取缓存,否则才会从网络获取

val file: File? =  Glide.with(context).downloadOnly().load(url).apply(RequestOptions().onlyRetrieveFromCache(true)).submit().get()

注意要在子线程操作

如何给图片添加蒙层

 private suspend fun addCoverToImage(resultUri: Uri): String {val filePath = withContext(Dispatchers.IO) {// 原路径, uri是从系统相册选择后的图片urival cropFile = File(PhotoUtil.getImageAbsolutePath(context, resultUri))// 目标路径val destPath = File(cropFile.parentFile, "${cropFile.nameWithoutExtension}_transform.${cropFile.extension}").absolutePath// 原图val srcBitmap = BitmapUtils.decodeSampledBitmapFromFile(file = cropFile.absolutePath, reqWidth = ScreenUtil.getScreenWidth(), reqHeight = ScreenUtil.getScreenHeight())// 方式1: 使用drawColor
//            val destBitmap =srcBitmap.copy(Bitmap.Config.ARGB_8888,true)
//            Canvas(destBitmap).drawColor(R.color.ty_black_60.asColor(), PorterDuff.Mode.SRC_ATOP)// 方式2: 使用colorFilterval destBitmap = Bitmap.createBitmap(srcBitmap.width, srcBitmap.height, Bitmap.Config.ARGB_8888)Canvas(destBitmap).drawBitmap(srcBitmap,0f,0f,Paint().apply { colorFilter = PorterDuffColorFilter(R.color.ty_black_60.asColor(), PorterDuff.Mode.SRC_ATOP) })// 重新保存bitmap到文件中BitmapUtils.writeToFile(destBitmap, destPath, 80)return@withContext destPath}// 返回修改后的图片路径return filePath}

如何实现第三方App的文本分享

public static void shareText(Context activity, String shareText, String pkgFilter,@Nullable Bundle extraData) {try {if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {// 处理分享结果的BroadcastReceiverIntent receiver = new Intent(activity, ShareBroadcastReceiver.class);receiver.setPackage(activity.getPackageName());// 添加参数,在ShareBroadcastReceiver收到结果的时候可以取出if (extraData != null) {receiver.putExtra("extraData", extraData);}int flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, 0, receiver, flag);// 分享的actionIntent intent = new Intent(Intent.ACTION_SEND);// 分享mimeTypeintent.setType("text/plain");// 分享的文本内容intent.putExtra(Intent.EXTRA_TEXT, shareText);// 如果有指定第三方App的包名,可以设置if (!StringUtils.isNullOrEmpty(pkgFilter)) {intent.setPackage(pkgFilter);}if (intent.resolveActivity(activity.getPackageManager()) != null) {// createChooser() 方法将 Intent 包装在一个新 Intent 中,并显示一个对话框,其中包含用户可以从中选择的应用程序列表。用户成功选择应用程序后,选定应用程序将处理包装的 Intent。// pendingIntent.getIntentSender() 则允许您指定一个 IntentSender 对象,以便在选择器对话框上选择应用程序之前,系统可以在用户选择之后返回结果给您的应用程序。activity.startActivity(Intent.createChooser(intent,"",pendingIntent.getIntentSender()));}} else {Intent sendIntent = new Intent();if (!StringUtils.isNullOrEmpty(pkgFilter)) {sendIntent.setPackage(pkgFilter);}sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);//shareText 为需要分享的内容sendIntent.setType("text/plain");sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);activity.startActivity(sendIntent);}} catch (Exception e) {e.printStackTrace();}}

ShareBroadcastReceiver的处理如下:

public class ShareBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {try {if (intent.getExtras() != null && intent.getExtras().keySet() != null && !intent.getExtras().keySet().isEmpty()) {Parcelable parcelableExtra = intent.getParcelableExtra(Intent.EXTRA_CHOSEN_COMPONENT);String packageName = null;if (parcelableExtra instanceof ComponentName) {packageName = ((ComponentName) parcelableExtra).getPackageName();Bundle extraData = intent.getBundleExtra("extraData");// todo 分享结果回调处理}}} catch (Exception e) {e.printStackTrace();}}}
}

Android实用小技巧(持续更新)相关推荐

  1. 小白使用word小技巧-持续更新(以论文服务为主)

    文章目录 写论文时的记录,本人键盘各位置图,仅供参考(事实上,使用word,按一下Alt,可以看到word屏幕上的快捷键提示,记不住就对着按就行了) 1.文字快捷键 : 2.文本的段落样式设置快捷键和 ...

  2. ASP.NET 开发小技巧 (持续更新)

    有时为一些小问题而去查资料.浪费太多的时间,为此把开发中经常遇到的一些小问题.记录下来.供日常开发查询用: 1.项目中使用Forms验证.而有些文件又不需要验证就能访问,最常见的是验证码文件或admi ...

  3. Python日常小技巧(持续更新中)

    目录 快速定位元组内容 对字典进行排序 json的获取(dumps,dump,loads,load) 查找字典中相同的key 统计列表中元素的个数 字典按输入顺序输出 历史记录的存储 对有多个分割符的 ...

  4. IAR 使用小技巧--持续更新

    1.复制和粘贴几行的部分代码 需求:有时候我们需要复制几行代码的后半部分,不需要复制前半部分. 方法:按住Alt键,再用鼠标拖动就可以复制和粘贴后半部分 2.常用快捷键 烧录程序:Ctrl + D 全 ...

  5. Maven打包小技巧--持续更新

    NO.1 跳过测试,打包指定环境 mvn clean install -Dmaven.test.skip=true -P dev 其中:clean将target目录中的文件移除: install根据配 ...

  6. Unity3D小功能 小技巧 小教程 小原理(持续更新...)

    Unity3D小功能 小技巧 小教程 小原理(持续更新...) 1.Unity的.NET版本是2.0 按道理来说,C#能用的功能Unity也能用,但是Unity的.NET却不是最新版 要是用一些别的D ...

  7. phpstudy mysql建表_MySQL_总结MySQL建表、查询优化的一些实用小技巧,MySQL建表阶段是非常重要的一 - phpStudy...

    总结MySQL建表.查询优化的一些实用小技巧 MySQL建表阶段是非常重要的一个环节,表结构的好坏.优劣直接影响着后续的管理维护,赶在明天上班前分享总结个人MySQL建表.MySQL查询优化积累的一些 ...

  8. Excel表格中一些你不知道的鼠标双击实用小技巧

    Excel表格中一些你不知道的鼠标双击实用小技巧 目录 Excel表格中一些你不知道的鼠标双击实用小技巧 1.鼠标双击自动填充整列公式, 鼠标放在单元格右下角,鼠标指针变成十字时双击鼠标右键,整列汇总 ...

  9. VC的若干实用小技巧

    Visual C++6.0(5.0)开发工具功能非常强大,但是对于初学者来说,却有很多细节的问题需要注意.作者搜集整理了以下一些实用小技巧,希望对初学者有所帮助. 1:使用vc开发项目时,常会遇到这种 ...

最新文章

  1. 第三组 通信一班 030 ISISv6
  2. 数组工具类Arrays
  3. IO多路转接之poll
  4. tcp http socket
  5. 【深度学习】Cifar-10-探究不同的改进策略对分类准确率提高
  6. J2ME J2SE J2EE
  7. java file数组 初始化_java从文件中读取数据并赋值给数组
  8. WIN7系统下如何设置护眼设置
  9. Kafka配置broker映射错误报错Discovered coordinator xxx rack: null
  10. 【总结】仰望星空,脚踏实地 2017.09-2018.02
  11. Linux性能测试工具之Disk(四)
  12. 快速了解Log4J (转)
  13. 行走在前端路上的一些想法
  14. Leetcode_35_Search Insert Position
  15. python一句代码生成26个英文字母
  16. vivado各版本的区别
  17. 服务发现-注册中心概述
  18. 爵士乐里全用13和弦吗?_用微妙的视差爵士化静态网页
  19. [ 深度学习 ] 胶囊网络(Capsule)
  20. 利用python爬虫实现:抖音短视频无水印视频下载

热门文章

  1. Cannot parse date 2023-01-16 09:48:12: while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ'
  2. matlab分块矩阵取某一块_MATLAB实现矩阵分块相乘
  3. 用Python群发邮件,含附件、excel内容读取,收件人列表读取等
  4. CheckPoint Security Checkup 中文支持
  5. 机器学习中的数学:概率统计
  6. 微信小程序开发入门就这么简单!
  7. 吃瓜教程task05 第6章 支持向量机
  8. c/c++ sizeof(数组名) 的解析 sizeof如何计算数组大小
  9. 解决idea控制台乱码问题
  10. 14年前,人生中编写的第一份计算机病毒。