RecyclerView
使用GridLayoutManager的时候,如果item的layout的高度设置成match parent,只能显示第一行。
使用GridLayoutManager的时候,设置了一行多少个,每个item所占的空间是一样的,也就是等分的,相当于item是在每个格里,item小的话,会靠左,可以调整item layout让内容居中,这样item在自己格子中居中。
我可以给RV add ItemDecoration 去控制每个item的位置,需要说明的是Rect的left、right、top、bottom控制的是这个item距它所处格子
的位置。
RecyclerView的宽度设置成wrap content,然后item layout的宽度也设置成wrap content,这样的话,所有item都仅靠在中间。
RecyclerView.ItemDecoration设置的时候,对应同一个RecyclerView,要避免重复设置,要么放在一个只执行一次的地方,要么add之前判断一下数量。
RecyclerView更新的一些问题
setAdapter、notifyDataSetChanged、notifyItemChanged都能实现更新的目的,关键是这些更新都不是及时的(有200ms到300ms),短时间调用多次更新,ViewHolder的bind只会执行一次,下面是个例子的执行流程。这样的话,就可能掩盖一些错误,比如一系列更新的时候正常,可是有时,单个更新就报空指针异常,这是因为单个更新,更新所需的数据是不完整的,有些数据就是null,只是多个更新的时候,数据已经不是null所以没报错,更新数据时一定注意空指针
。具体发起更新到执行,中间的时间,是个怎么个过程,这块得研究一下。
2021-09-14 18:35:40.378 1273-1273/com.sohu.sohuvideo E/lzy: notifyItemChanged
2021-09-14 18:35:40.379 1273-1273/com.sohu.sohuvideo E/lzy: notifyDataSetChanged
2021-09-14 18:35:40.379 1273-1273/com.sohu.sohuvideo E/lzy: notifyDataSetChanged
2021-09-14 18:35:40.415 1273-1273/com.sohu.sohuvideo E/lzy: onBindView
还有一个问题是:在更新时,不可见的item是不更新的,只有可见的时候才更新,这个也能掩盖空指针问题。
这就是为什么,都是刷新整个界面,为啥有时会崩溃,因为RecyclerView的可见区域不同。
结论就是,在ViewHolder里面使用数据,一定要进行非空判断
举个实际遇到的情况:
自己有个大的ViewHodler里面内容很多,里面内容是不同接口的数据组成的,每个接口回来的时间是不确定的,而且每个接口回来,不一定都得整个更新ViewHolder,这样的话,我们就得控制不同情况更新ViewHolder的不同部分。
在Item可见的时候,执行是没有问题的,这是因为,不管接口回来的快慢,都会更新ViewHolder的,最终展示是正确的。但是,如果item不在可见区域的话,更新需求就会覆盖
,如果只用一个变量记录更新区域,这样的话,等item进入可见区域更新的时候,更新需求就会丢失,所以我们得小心处理,各位关注这种情况。
下面是更新控制的代码:
public class VipCommodityUiData{
public static final int UPDATE_NONE = 0;
public static final int UPDATE_ALL = 1;
public static final int UPDATE_ONLY_COUPON_COUNT = 2;
public static final int UPDATE_ONLY_COUPON_PAY = 3;
@IntDef({UPDATE_NONE, UPDATE_ALL, UPDATE_ONLY_COUPON_COUNT, UPDATE_ONLY_COUPON_PAY})
@Retention(RetentionPolicy.SOURCE)
public @interface UpdateType {}
public @UpdateType int getUpdateType() {
int tmpUpdateType = mUpdateType;
mUpdateType = UPDATE_NONE;
return tmpUpdateType;
}
public void setUpdateType(@UpdateType int updateType) {
if (mUpdateType == UPDATE_NONE) {
mUpdateType = updateType;
} else {
if (mUpdateType != UPDATE_ALL) {
/**
* mUpdateType不等于UPDATE_ALL,说明已经是UPDATE_ONLY_COUPON_COUNT或UPDATE_ONLY_COUPON_PAY,还没执行更新
* 现在还需要更新,不管更新哪部分,都全量更新
*/
mUpdateType = UPDATE_ALL;
}
}
}
private @UpdateType int mUpdateType = UPDATE_NONE;
}
-
notifyItemRemoved无效的问题
一开始还以为是notifyItemRemoved本身出了问题,其实,还是业务逻辑出错了。
我们知道在调用notifyItemRemoved之前,需要把数据从数据list中删除,在业务中,由于在一些时机更新了数据list,导致了Adapter内外维护了list不是同一个对象了,在业务中,删除了adapter外部list中的数据,adapter里面list中的数据其实没有删除,所以导致notifyItemRemoved无效。
结论:如果发现notifyItemRemoved无效的时候,一定要关注Adapter里面的list有没有真正删除数据 -
notifyDataSetChanged调用多次
notifyDataSetChanged调用多次,会导致Adapter的onBindViewHolder从0开始执行多次,要是在onBindViewHolder中根据position做了一些逻辑,这样可能就会出现,尤其是在父类中的onBindViewHolder做了一些逻辑
在Android上由于屏幕大小有很多种类,所以我们不能固定图片大小,最好的做法是,两边和间距是固定的,宽度和高度成比例,这样就能适配所有屏幕。
有个问题是,是需要动态计算出图片宽度设置给图片,这样才能正常,指望recyclerView自己计算是不行的,它没那么智能。结果会成这样
这个问题,上面的解决方式不是真正的解决方式。真正的原因看下面的文章
ItemDecoration实现等分间距
RecyclerView GridLayoutManager 等分间距
RecyclerView GridLayoutManager 等分间距
Android Recyclerview GridLayoutManager column spacing
我们知道RecyclerView中ViewHolder是复用的,需要注意的是,被复用的ViewHolder是持有之前填充的老数据的,所以需要把ViewHolder中的数据刷新一下,这个没问题。但是,如果ViewHolder中有RecyclerView,而且这个RecyclerView使用了ItemDecoration,那么就需要格外注意了。因为ItemDecoration是add,不是set,add几次就有几个ItemDecoration,所以,不能在Adapter的OnBindViewHolder中去刷新ViewHolder的时候,给ViewHolder中的RecyclerView add ItemDecoration,因为这样会导致重复add ItemDecoration的问题。所以得把add 操作放到ViewHolder实例化的时候。
class UserGetVipColumnViewHolder(private val viewBinding: ItemGetVipColumnBinding, private val context: Context): UserGetVipBaseViewHolder(viewBinding) {
init {
viewBinding.rvHotVideos.isNestedScrollingEnabled = false
viewBinding.rvHotVideos.layoutManager = GridLayoutManager(context, SPAN_COUNT)
viewBinding.rvHotVideos.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
val column = position % SPAN_COUNT
val mColumnSpacing = context.resources.getDimensionPixelSize(R.dimen.dp_11)
outRect.left = column * (mColumnSpacing / SPAN_COUNT)
outRect.right = mColumnSpacing - (column + 1) * (mColumnSpacing / SPAN_COUNT)
}
})
}
...
}
TextView
TextView使用背景图的问题:
把一个9图设置给Textview,9图没有用,大小会根据文字内容的大小。
在TextView外面套一个FrameLayout,给FrameLayout设置9图,FrameLayout最小大小是9图的大小
<LinearLayout
android:id="@+id/layout_discount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:background="@drawable/vip_tag">
<TextView
android:id="@+id/tv_discounts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="优惠"
android:layout_gravity="center"
android:textSize="@dimen/text_size_9"
android:layout_marginVertical="@dimen/dp_1"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:textColor="@color/white" />
</LinearLayout>
上面配置效果是这样的,LinearLayout的高度是图片大小是一样的,9图的内容区域是除了角的上半部分,所以TextView设置的center也是展示在上半部分
<TextView
android:id="@+id/tv_super_tab_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:includeFontPadding="false"
tools:text="电视端也能看"
android:padding="@dimen/dp_3"
android:background="@drawable/vip_tag2"
android:textSize="9sp"
android:textColor="@color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
给TextView直接设置9图,9图的意义就失效了
有个EditText,我们想让这个View的高度是根据内容高度自适应,设置wrap_content,可是,有时候在没内容的情况下,高度变成了0,为了避免这种情况的出现,可以设置一个最小高度
<EditText
android:id="@+id/et_text_input"
android:layout_width="match_parent"
android:textSize="@dimen/video_edit_text_size"
android:textColor="@color/txt_white"
android:textCursorDrawable="@drawable/video_edit_text_cursor"
android:layout_height="wrap_content"
android:minWidth="2dp"
android:minHeight="@dimen/dp_20"
android:gravity="center"
android:background="@null"
android:layout_marginTop="180dp"
android:layout_gravity="top|center_horizontal" />
Window
我们知道Activity和Popwindow都是有window的,然后View都是放在window里的,为了让View正常,window的大小不要设置成0或1。
遇到一个问题,要根据Activity 中android.R.id.content View来定位显示一个pop,我们知道这个View首先得attach到window上,才能让pop来锚定,也就是getWindowToken() != null,可是发现getWindowToken一直是null,最后折腾了很久,才发现是在Activity中对window进行了处理
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.height = 1;
params.width = 1;
window.setAttributes(params);
把window大小设置成了1,把这个得去掉
还遇到一个问题,在onCreate中popwindow,getWindowToken()总是null,这个是因为view还没有attach到window上,解决办法是,延迟了200ms来处理
让activity透明,但是window大小不变的设置
<style name="updateDialogStyle2">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
</style>
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1056)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:381)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:1478)
at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1235)
at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1202)
at com.sohu.sohuvideo.system.permission.PermissionHintWindow.showHint(PermissionHintWindow.java:54)
at com.sohu.sohuvideo.system.permission.PermissionWindowHelper.showPermissionHint(PermissionWindowHelper.java:38)
at com.sohu.sohuvideo.ui.VideoRequestPermissionActivity.requestPermissions(VideoRequestPermissionActivity.java:183)
at com.sohu.sohuvideo.ui.VideoRequestPermissionActivity.requestPermission(VideoRequestPermissionActivity.java:159)
at com.sohu.sohuvideo.ui.VideoRequestPermissionActivity.onCreate(VideoRequestPermissionActivity.java:127)
at android.app.Activity.performCreate(Activity.java:7326)
at android.app.Activity.performCreate(Activity.java:7317)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3072)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3235)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6990)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
自动拆装箱
java是能够自动拆装箱的,但是一个null在自动拆装箱的场景下就会出错,尤其是把包装类存在集合中的业务场景下
payRMB = priceMap.get(payMethod);
改成
Integer priceInteger = priceMap.get(payMethod);
payRMB = priceInteger != null ? priceInteger : 0;