Android开发各种奇怪问题记录

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;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
步数记录器可以通过以下步骤实现: 1. 添加传感器权限和计步器传感器类型声明到AndroidManifest.xml文件中。 ``` <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-feature android:name="android.hardware.sensor.stepcounter" /> <uses-feature android:name="android.hardware.sensor.stepdetector" /> ``` 2. 在Activity中获取传感器服务的实例。 ``` SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); ``` 3. 获取计步器传感器的实例,并注册监听器。 ``` Sensor stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); sensorManager.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_NORMAL); ``` 4. 在监听器中,实现步数的记录和更新。 ``` @Override public void onSensorChanged(SensorEvent event) { Sensor sensor = event.sensor; if (sensor.getType() == Sensor.TYPE_STEP_COUNTER) { int stepCount = (int) event.values[0]; updateStepCount(stepCount); } } private void updateStepCount(int stepCount) { // 记录步数并更新UI mStepCount = stepCount; mStepCountTextView.setText(String.valueOf(mStepCount)); } ``` 5. 在Activity中,可以添加计时器,定期记录步数并更新UI。 ``` private Timer mTimer; private TimerTask mTimerTask; private void startRecord() { mTimer = new Timer(); mTimerTask = new TimerTask() { @Override public void run() { updateStepCount(mStepDetector.getStepCount()); } }; mTimer.schedule(mTimerTask, 0, 1000); } private void stopRecord() { if (mTimer != null) { mTimer.cancel(); mTimer = null; } if (mTimerTask != null) { mTimerTask.cancel(); mTimerTask = null; } } ``` 以上是一个简单的步数记录器的实现示例。需要注意的是,计步器传感器的精度和准确性可能会受到设备硬件和软件版本的影响,因此需要进行测试和验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值