Paint的细节用法
1、设置笔帽
mPaint.setStrokeCap(Paint.Cap.BUTT);//没有
mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形
mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形
2、设置滤镜
1、模糊遮罩滤镜(BlurMaskFilter)
2、浮雕遮罩滤镜(EmbossMaskFilter)
3、设置线条汇合处
mPaint.setStrokeJoin(Paint.Join.MITER);//锐角
mPaint.setStrokeJoin(Paint.Join.ROUND);//圆弧
mPaint.setStrokeJoin(Paint.Join.BEVEL);//直线
4、文字相关
//获得字符行间距
mPaint.getFontSpacing();
//获得字符之间的间距
mPaint.getLetterSpacing();
//设置文本删除线
mPaint.setStrikeThruText(true);
//设置下划线
mPaint.setUnderlineText(true);
//设置文本大小
mPaint.setTextSize(textSize);
//设置字体类型
mPaint.setTypeface(Typeface.ITALIC);
//加载自定义字体
Typeface.create(familyName, style)
//文字倾斜,官方推荐的-0.25f是斜体
mPaint.setTextSkewX(-0.25f);
//文本对齐方式
mPaint.setTextAlign(Align.LEFT);
mPaint.setTextAlign(Align.CENTER);
mPaint.setTextAlign(Align.RIGHT);
//计算制定长度的字符串(字符长度、字符个数、真实的长度)
int breadText = mPaint.breakText(text, measureForwards, maxWidth, measuredWidth)
float[] measuredWidth = new float[1];
int breakText = mPaint.breakText(str, true, 200, measuredWidth);
Log.i("TAG", "breakText="+breakText+", str.length()="+str.length()+", measredWidth:"+measuredWidth[0]);
//获取文本的矩形区域(宽高)
mPaint.getTextBounds(text, index, count, bounds)
//获取文本的宽度(比较粗略的结果)
float measureText = mPaint.measureText(str);
//获取文本的宽度(比较精准的)
float[] measuredWidth = new float[10];
int textWidths = mPaint.getTextWidths(str, measuredWidth);
Log.i("TAG", "measureText:"+measureText+", textWidths:"+textWidths);
5、基线相关
FontMetrics fontMetrics = mPaint.getFontMetrics();
//绘制文本在View的左上角(基线Y的计算公式)
float baselineY = top(已知) - fontMetrics.top;
//绘制文本的在View的中间(基线Y的计算公式)
float baselineY = centerY(已知)+ (fontMetrics.bottom-fontMetrics.top)/2 - fontMetrics.bottom;
//绘制文本的在View的中间(或者是这样的)
float baselineY = centerY(已知)+ (mPaint.descent()+mPaint.ascent())/2;
6、渲染相关
LinearGradient线性渲染
RadialGradient环形渲染、可做水波纹效果,充电水波纹扩散效果、调色板
SweepGradient梯度渲染(扫描渲染)、可做微信等雷达扫描效果
ComposeShader组合渲染
7、ColorMatrix(五阶矩阵)
//色彩的平移运算(加法运算)
//色彩的缩放运算(乘法运算)
//构造方法
ColorMatrix matrix = new ColorMatrix(new float[]{});
ColorMatrix matrix = new ColorMatrix();
matrix.set(src)
//设置色彩的缩放函数
matrix.setScale(1, 1, 1.4f, 1);
//设置饱和度(1,是原来不变;0灰色;>1增加饱和度)
matrix.setSaturation(progress);
//色彩旋转函数(axis,代表绕哪一个轴旋转,0红色,1绿色,2蓝色,degrees:旋转的度数)
matrix.setRotate(0, progress);
//ColorFilter使用的子类
ColorMatrixColorFilter:色彩矩阵的颜色顾虑器。
LightingColorFilter:过滤颜色和增强色彩的方法。(光照颜色过滤器)
PorterDuffColorFilter:图形混合滤镜(图形学的一个理论飞跃)
Canvas.drawPath
将文本画在Path上,可以形成圆弧的文本
Path path = new Path();
//Path.Direction.CW,沿外环;Path.Direction.CCW,沿内环
path.addCircle(500, 500, 200, Path.Direction.CW);
mPaint.setTextSize(50);
// 绘制路径
canvas.drawPath(path, mPaint);
String text = "大家好,我是Hensen";
canvas.drawTextOnPath(text, path, 0f, 0f, mPaint);
Home键回来后会先拉起闪屏的问题
在某些机型上,第一次安装点击icon启动应用,然后home键退到后台,再次点击icon启动应用,会先拉起闪屏的问题
class SplashActivity : BaseVMActivity<SplashViewModel>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* 如果此页面不是任务栈中根activity,说明应用有其他activity存在,此时不需要拉起splash页面,任务栈被切到前台会自动显示栈顶activity
*/
if (!isTaskRoot) {
//如果是外部跳转的Intent也会跑到这里,需要特殊处理
SchemaUtils.gotoArouter(intent)
finish()
return
}
setContentView(R.layout.activity_splash)
}
}
java.lang.NoSuchMethodError
在我们崩溃系统中会看到这种崩溃
java.lang.NoSuchMethodError: No static method Desc(I)Ljava/lang/String; in class Lcom/yy/platform/baseservice/ConstCode$SrvResCode; or its super classes (declaration of 'com.yy.platform.baseservice.ConstCode$SrvResCode' appears in /data/app/com.yy.yinfu-0eluUMz7kO0sb_RhAZpHNw==/split_lib_dependencies_apk.apk!classes2.dex)
从崩溃的日志上翻译出来的意思是,找不到该类的方法,但是又在classes2.dex发现了这个类的方法,这是因为在多Dex的情况下,我们启动需要的class都需要放在maindex中,由于这个类放在了classes2.dex,所以报出找不到的崩溃。由于我们是Debug包,所以不加处理,但是在Release包中我们对混淆过后的这个类放在maindex中
release {
......
multiDexKeepProguard file('multidex.keep')
......
}
在multidex.keep
中加入我们找不到的class即可
-keep class com.xxx.xxx.xxx$xxxx
Fragment嵌套SurfaceView切换时闪黑屏
最近在开发的时候用的是ViewPager+Fragment进行底部Tab页的切换,但在最后一个Tab中Fragment嵌入了SurfaceView,用于Flutter开发,不料在切换Tab的时候SurfaceView的Tab会导致其他Tab切换过去的时候显示黑屏或者白屏等情况(黑屏、白屏主要取决你设置的Theme),解决方法是在SurfaceView上进行这两句话设置
setZOrderOnTop(true);
setZOrderMediaOverlay(true);
mHolder.setFormat(PixelFormat.TRANSPARENT);
RecyclerView实现点击拖拽而不是长按拖拽
最近做项目,需要在3张图片中,左右拖动能更换位置,再此,封装了一个左右拖拽的辅助类,将RecyclerView设置不能左右滑动,但是点击就能左右拖动Item,并交换左右图片
class ListeningKonwPeopleRecyclerViewController(var context: Context,
var recyclerView: RecyclerView,
var data: ArrayList<LisnteningKonwPeopleInfo>) {
var helper: ItemTouchHelper? = null
var adapter: ListeningKonwPeopleCoverAdapter? = null
var layoutManager: LinearLayoutManager? = null
init {
layoutManager = object : LinearLayoutManager(context, HORIZONTAL, false) {
override fun canScrollHorizontally(): Boolean {
return false
}
}
adapter = ListeningKonwPeopleCoverAdapter(data)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
helper = ItemTouchHelper(
object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
val dragFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT//拖拽
return ItemTouchHelper.Callback.makeMovementFlags(dragFlags, 0)
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
//滑动事件
Collections.swap(data, viewHolder.adapterPosition, target.adapterPosition)
adapter?.notifyItemMoved(viewHolder.adapterPosition, target.adapterPosition)
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
//侧滑事件
}
override fun isLongPressDragEnabled(): Boolean {
return false
}
})
helper?.attachToRecyclerView(recyclerView)
}
fun attach() {
adapter?.setOnTouchClickListener(
object : ListeningKonwPeopleCoverAdapter.OnTouchClickListener {
override fun onTouchClick(info: LisnteningKonwPeopleInfo, holder: ListeningKonwPeopleCoverAdapter.VH) {
helper?.startDrag(holder)
}
})
}
fun dettach() {
adapter?.setOnTouchClickListener(
object : ListeningKonwPeopleCoverAdapter.OnTouchClickListener {
override fun onTouchClick(info: LisnteningKonwPeopleInfo, holder: ListeningKonwPeopleCoverAdapter.VH) {
return
}
})
}
}
在Adapter
中设置OnTouchClickListener
的实现
override fun onBindViewHolder(holder: VH, position: Int) {
......
holder.itemView.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
mOnTouchClickListener?.onTouchClick(info, holder)
}
return@setOnTouchListener false
}
}
使用点击拖拽功能
activity?.let {
recyclerViewController = ListeningKonwPeopleRecyclerViewController(it, view.rv_cover, data)
recyclerViewController?.attach()
}
取消点击拖拽功能
activity?.let {
recyclerViewController = ListeningKonwPeopleRecyclerViewController(it, view.rv_cover, data)
recyclerViewController?.dettach()
}
键盘弹起不会顶上去视图
键盘弹起要让视图跟着弹起解决方案太多,但是有个比较简单的方案,可以适合大多数场景,这场适合于键盘盖住视图的情况下使用,在盖住的布局中使用android:fitsSystemWindows="true"
即可
<com.yy.mobile.memoryrecycle.views.YYLinearLayout
android:id="@+id/input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/common_color_11"
android:fitsSystemWindows="true"
android:gravity="center"
android:minHeight="@dimen/moment_chat_input_height"
android:orientation="vertical"
android:visibility="gone">
为什么View.startAnimation不起作用?
看了一下View里面的源码,发现确实有一些地方判断了如果不是visible的,那么就不调用invalidate方法,也就不会去处理Animation的事情。以后startAnimation的时候,—定要选─个总是可见的View哦
正确的用法
private fun startShowAnimation(from: AnimationFrom,
animationView: View,
centerX: Float,
centerY: Float) {
animationView.visibility = View.VISIBLE
val scaleAnimation = ScaleAnimation(0f, 1f, 0f, 1f, centerX, centerY)
scaleAnimation.duration = 250
scaleAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
}
override fun onAnimationStart(animation: Animation?) {
}
})
animationView.startAnimation(scaleAnimation)
}
横竖屏切换沉浸式失效
- 查问题时间:3天,问题与window焦点触摸有关
- 需要效果:在沉浸式模式下,横屏需要隐藏导航栏,竖屏需要显示导航栏
- 出现问题:在横屏,点击屏幕后导航栏消失,切换为竖屏时,沉浸式效果失效,导航栏消失,只要点击屏幕即可重新恢复沉浸式
- 解决问题:在沉浸式实现中套一层
getWindow().getDecorView().post(new Runnable() {}
即可解决
private void initImmersive() {
if (isLandscape()) {
//增加沉浸式
ImmersionBar.with(this)
.navigationBarEnable(false)
.transparentStatusBar()
.statusBarDarkFont(false)
.init();
//横屏需要隐藏底部导航栏
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
if (!checkActivityValid()) {
return;
}
if (getContext() instanceof Activity && NavBarUtils.hasNavBar(getContext())) {
NavBarUtils.hideBottomNav((Activity) getContext());
}
}
});
} else {
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
if (!checkActivityValid()) {
return;
}
if (getContext() instanceof Activity) {
//增加沉浸式
ImmersionBar.with((Activity) getContext())
.navigationBarEnable(false)
.transparentStatusBar()
.statusBarDarkFont(false)
.init();
}
}
});
}
}
在特殊情况下,设置View的Gone无效
- 背景:在拉取视频流的时候,将视频流的父布局Gone掉,但视频流拉流成功后,视频流的布局还是会出来,也就是说父布局Gone掉,子布局因为拉流成功而显示出来
- 原因:由于某些代码调用了父布局Gone,但流的视频布局是等拉流成功后才addview进来,导致父布局先Gone,流布局add进来后是visible,这时,流就会出来
- 解决:调整下逻辑,先等流进来后,再Gone掉,或者不要拉流的逻辑
在水滴屏情况下,横屏键盘适配处理
- 背景:在水滴屏手机上,发现横屏之后,弹起的键盘无法延伸到水滴屏内,跟水滴屏相隔有条缝,但是整个布局依然在水滴屏内
- 原因:水滴屏需要额外适配
- 解决:在横竖屏的时候进行水滴屏的设置处理
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (mIsLandscape) {
getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
} else {
getWindow().getAttributes().layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
}
}
}
引用 https://blog.csdn.net/guolin_blog/article/details/103112795
Android 9.0系统中提供了3种layoutInDisplayCutoutMode属性来允许应用自主决定该如何对刘海屏设备进行适配:
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:这是一种默认的属性,在不进行明确指定的情况下,系统会自动使用这种属性。这种属性允许应用程序的内容在竖屏模式下自动延伸到刘海区域,而在横屏模式下则不会延伸到刘海区域
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:这种属性表示,不管手机处于横屏还是竖屏模式,都会允许应用程序的内容延伸到刘海区域
- LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:这种属性表示,永远不允许应用程序的内容延伸到刘海区域
AS一打开hprof文件就出现卡机,卡死
解决:在AS设置中,将内存变大
在关闭页面的onPause周期中处理销毁逻辑
- 遇到问题:关闭当前页面的时候,生命周期会先走底下页面的onResume,再走关闭页面的onDestroy方法
- 具体场景:客户端进入播放器页面播放视频,在关闭视频页的时候,onDestroy执行打开视频声音,首页不需要视频声音,在onResume的时候首页启动静默播放,导致有声音出现
- 解决方法:在onPause中做销毁页面的逻辑,具体如下代码
@Override
protected void onPause() {
super.onPause();
if (isFinishing()) {
//退出当前页
......
}
}
View的Touch事件区域判断
/**
* 判断触摸事件是否在对应的view上
*
* @param e 触摸事件
* @param v 对应的View
* @return true 当且仅当触摸事件e是发生在v上面
*/
public static boolean isOnView(MotionEvent e, View v) {
Rect r = new Rect();
v.getGlobalVisibleRect(r);
return r.left <= e.getRawX() && e.getRawX() <= r.right
&&
r.top <= e.getRawY() && e.getRawY() <= r.bottom;
}
View的Path的点击事件区域判断
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
for (int i = 0; i < paths.size(); i++) {
Path path = paths.get(i);
Region region = new Region();
//将path和region的两个区域取交集
region.setPath(path, mGlobalRegion); //mGlobalRegion当前View的区域
if (region.contains(x, y)) {
if (null != mRegionClickListener) {
mRegionClickListener.onRegionClick(i);
}
break;
}
}
break;
}
return super.onTouchEvent(event);
}
如何在传递一个具有多个属性值的枚举类
当我们经常需要在一个接口中传递一个枚举,但有希望一个参数可以映射出多个值
public enum Action {
ACTION_LOGIN_IN("0", "登录"),
ACTION_LOGIN_IN_SUCCESS("1", "登录成功"),
ACTION_LOGIN_OUT("2", "退出登录");
private String a;
private String b;
private Action(String a, String b) {
this.a = a;
this.b = b;
}
public String getValue() {
return this.a;
}
public String getName() {
return this.b;
}
}
弹窗前,检查Activity比较严谨的做法
- 背景:在需要调用显示弹窗或者消失弹窗的时候,避免调用的弹窗对象已经被销毁,导致调用弹窗崩溃的问题
- 解决:在调显示和消失弹窗之前,增加
checkActivityValid()
判断
@TargetApi(17)
private boolean checkActivityValid() {
if (mContext == null || mContext.get() == null) {
MLog.warn(TAG, "Fragment " + this + " not attached to Activity");
return false;
}
if (mDialog != null && mDialog.getWindow() == null) {
MLog.warn(TAG, "window null");
return false;
}
if (mContext.get() instanceof Activity && ((Activity) mContext.get()).isFinishing()) {
MLog.warn(TAG, "activity is finishing");
return false;
}
if (Build.VERSION.SDK_INT >= 17 && mContext.get() instanceof Activity && ((Activity) mContext.get()).isDestroyed()) {
MLog.warn(TAG, "activity is isDestroyed");
return false;
}
return true;
}
RxJava事件解决防抖动问题
- 背景:短时间的快速点击事件只响应一次,防止它多次响应点击事件
- 解决:通过Rxjava自带的方法,写一个工具类来使用
/**
* 工具类里面
*/
public static Observable<Object> clicks(final View view, long millseconds) {
final ViewClicksObservableOnSubscrib viewClicksObservable = new ViewClicksObservableOnSubscrib();
if (view != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObservableEmitter<Object> emitter = viewClicksObservable.getEmitter();
if (emitter != null && !emitter.isDisposed()) {
emitter.onNext(1);
}
}
});
}
return Observable.create(viewClicksObservable).doOnDispose(new Action() {
@Override
public void run() throws Exception {
if (view != null) {
view.setOnClickListener(null);
}
}
}).throttleFirst(millseconds, TimeUnit.MILLISECONDS);
}
static class ViewClicksObservableOnSubscrib implements ObservableOnSubscribe<Object> {
ObservableEmitter<Object> emitter;
public ObservableEmitter<Object> getEmitter() {
return emitter;
}
@Override
public void subscribe(ObservableEmitter<Object> e) throws Exception {
emitter = e;
}
}
项目的使用
ViewUtils.clicks(startBtn, 1000)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
}
虚拟按键判断
/**
* 是否显示了虚拟按键
*/
public static boolean isNavigationBarShow(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Display display = ((FragmentActivity) context).getWindowManager().getDefaultDisplay();
Point size = new Point();
Point realSize = new Point();
display.getSize(size);
display.getRealSize(realSize);
return size.y != realSize.y;
} else {
boolean menu = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
if (menu || back) {
return false;
}
return true;
}
}
输入法弹起时,对应EditText跟着往上移动
- 背景:在做输入法发言的时候,EditText弹起的时候,对应的输入框视图可以跟着输入法一起弹起
- 解决:在输入调用的时候,传入对应的
editText
对象作为参数
private void showSoftKeyboard(EditText editText) {
if (editText != null) {
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
editText.requestFocus();
//调用系统输入法
InputMethodManager inputManager = (InputMethodManager) mContext.get().getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputManager != null) {
inputManager.showSoftInput(editText, 0);
}
}
}