概述

最近安卓自定义view的知识看的很熟,但是却很久没动手了,这几天用kotlin手撕了原先一个左滑删除的RecyclerView,居然弄得有点懵逼。后面又慢慢改进、加东西,发现这样一个例子下来,自定义View以及事件分发的知识居然覆盖的差不多了,所以有了写博客的想法。下面我会从我的思路一点点的写下去,碰到的各种问题就是知识的实际应用了,通过问题学知识,我觉得这样的方式非常好!

需求

这里我要做的是一个左滑删除列表项的功能,之前拿过一个别人的用,所以有了一点思路,但是不深刻。于是我开始从零出发,先写个大致思路再一步步去解决,首先肯定的是通过继承RecyclerView去实现,后面思路大致如下:

  • 在 down 事件中,判断在列表内位置,得到对应 item
  • 拦截 move 事件,item 跟随滑动,最大距离为删除按钮长度
  • 在 up 事件中,确定最终状态,固定 item 位置

编写代码I

根据上面三点思路,我刷刷地就写下了下面的代码:

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //流畅滑动
    private var mScroller = Scroller(context)
    //当前选中item
    private var mItem: ViewGroup? = null
    //上次按下横坐标
    private var mLastX = 0f
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
           when(e.action) {
               MotionEvent.ACTION_DOWN -> {
                   //获取点击位置
                   getSelectItem(e)
                   //设置点击的横坐标
                   mLastX = e.x
               }
               MotionEvent.ACTION_MOVE -> {
                   //不管左右都应该让item跟随滑动
                   moveItem(e)
                   //拦截事件
                   return true
               }
               MotionEvent.ACTION_UP -> {
                   //判断结果
                   stopMove(e)
               }
           }
        }
        return super.onInterceptTouchEvent(e)
    }
    //滑动结束
    //版本一:判断一下结束的位置,补充或恢复位置
    private fun stopMove(e: MotionEvent) {
        mItem?.let {
            val dx = e.x - mLastX
            //如果移动过半了,应该判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(dx) >= deleteWidth / 2) {
                //触发移动
                val left = if (dx > 0) {
                    deleteWidth - dx
                }else {
                    - deleteWidth   dx
                }
                mScroller.startScroll(0, 0, left.toInt(),0)
                invalidate()
            }else {
                //如果移动没过半应该恢复状态
                mScroller.startScroll(0, 0, - dx.toInt(),0)
                invalidate()
            }
            //清除状态
            mLastX = 0f
            mItem = null
        }
    }
    //移动item
    //版本一:绝对值小于删除按钮长度随便移动,大于则不移动
    private fun moveItem(e: MotionEvent) {
        mItem?.let {
            val dx = e.x - mLastX
            //这里默认最后一个view是删除按钮
            if (abs(dx) < it.getChildAt(it.childCount - 1).width) {
                //触发移动
                mScroller.startScroll(0, 0, dx.toInt(), 0)
                invalidate()
            }
        }
    }
    //获取点击位置
    //版本一:通过点击的y坐标除于item高度得出
    private fun getSelectItem(e: MotionEvent) {
        val firstChild = getChildAt(0)
        firstChild?.let {
            val pos = (e.x / firstChild.height).toInt()
            mItem = getChildAt(pos) as ViewGroup
        }
    }
    //流畅地滑动
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollBy(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

注意啊,这里的代码是没法用的,滑动后选不中正确的item,距离也有问题,所以里面有很多问题!

kotlin的构造

其实上来最懵逼的就是kotlin的构造函数,自己写了几次,感觉都不对,还是搜了下,有两种写法,我还是觉得使用JvmOverloads的比较方便,不过好像在API版本>21时还有个defStyleRes,我这就不相叙了,可以查资料。

获取的item位置不对

这里获取的item明显不对,其实这个问题很好发现,因为事件的x是屏幕的x啊,这里使用列表去计算明显不行,而且考虑了可见性吗?考虑可滑动隐藏了吗?考虑了第一个item子显示部分吗?

结合上面这些问题,应该如何去正确获取item的位置呢?看下面代码:

    private fun getSelectItem(e: MotionEvent) {
        val frame = Rect()
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }

这里参考了别人的代码,通过遍历子item,检查事件坐标是否在其中,在的话得到选中的item,不再需要position了,还是挺好理解的。

移动的计算不对

上面的代码将mLastX只记录down事件,而每次的是事件和dwon事件横坐标差值,明显错了。

首先mLastX这里应该记录的是每个事件的x,包含move的事件,移动的差值应该是一个小的差值。

MotionEvent.ACTION_MOVE -> {
    //移动控件
    moveItem(e)
    //更新点击的横坐标
    mLastX = e.x
    //拦截事件
    return true
}
    private fun moveItem(e: MotionEvent) {
        mItem?.let {
            val dx = mLastX - e.x
            //检查mItem移动后应该在[-deleteLength, 0]内
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if ((it.scrollX   dx) <= deleteWidth && (it.scrollX   dx) >= 0) {
                //触发移动
                it.scrollBy(dx.toInt(), 0)
            }
        }
    }

滑动结束结束判断不对

上面的mLastX修改后,滑动结束结束的判断不对,而且原本就是不对的哈!mScroller的移动就错了,正确的看下面:

    private fun stopMove() {
        mItem?.let {
            //如果移动过半了,应该判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(it.scrollX) >= deleteWidth / 2) {
                //触发移动至完全展开
                mScroller.startScroll(it.scrollX, 0, - deleteWidth,0)
                invalidate()
            }else {
                //如果移动没过半应该恢复状态
                mScroller.startScroll(it.scrollX, 0, 0,0)
                invalidate()
            }
            //清除状态
            mLastX = 0f
            mItem = null
        }
    }

编写代码II

改完上面代码大致就有了第二版,下面看全部代码,看看还有什么问题啊:

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //流畅滑动
    private var mScroller = Scroller(context)
    //当前选中item
    private var mItem: ViewGroup? = null
    //上次按下横坐标
    private var mLastX = 0f
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when(e.action) {
               MotionEvent.ACTION_DOWN -> {
                   //获取点击位置
                   getSelectItem(e)
                   //设置点击的横坐标
                   mLastX = e.x
               }
               MotionEvent.ACTION_MOVE -> {
                   //移动控件
                   moveItem(e)
                   //更新点击的横坐标
                   mLastX = e.x
                   //拦截事件
                   return true
               }
               MotionEvent.ACTION_UP -> {
                   //判断结果
                   stopMove()
               }
           }
        }
        return super.onInterceptTouchEvent(e)
    }
    //滑动结束
    //版本一:判断一下结束的位置,补充或恢复位置
    //问题:mLast不应该是down的位置
    //版本二:
    private fun stopMove() {
        mItem?.let {
            //如果移动过半了,应该判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(it.scrollX) >= deleteWidth / 2) {
                //触发移动至完全展开
                mScroller.startScroll(it.scrollX, 0, - deleteWidth,0)
                invalidate()
            }else {
                //如果移动没过半应该恢复状态
                mScroller.startScroll(it.scrollX, 0, 0,0)
                invalidate()
            }
            //清除状态
            mLastX = 0f
            mItem = null
        }
    }
    //移动item
    //版本一:绝对值小于删除按钮长度随便移动,大于则不移动
    //问题:移动方向反了,而且左右可以滑动,没有限定住范围,mLast只是记住down的位置
    //版本二:通过整体移动的数值,和每次更新的数值,判断是否在范围内,再移动
    private fun moveItem(e: MotionEvent) {
        mItem?.let {
            val dx = mLastX - e.x
            //检查mItem移动后应该在[-deleteLength, 0]内
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if ((it.scrollX   dx) <= deleteWidth && (it.scrollX   dx) >= 0) {
                //触发移动
                it.scrollBy(dx.toInt(), 0)
            }
        }
    }
    //获取点击位置
    //版本一:通过点击的y坐标除于item高度得出
    //问题:没考虑列表项的可见性、列表滑动的情况,并且x和屏幕有关不仅仅是列表
    //版本二:通过遍历子view检查事件在哪个view内,得到点击的item
    private fun getSelectItem(e: MotionEvent) {
        //获得第一个可见的item的position
        val frame = Rect()
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }
    //流畅地滑动
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollBy(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

代码改完,运行,诶,怎么只能滑动一小下?打断点试一下,选中的item正确了,但是怎么ACTION_MOVE只触发一次?怎么ACTION_UP不触发呢?这里就要注意下ACTION_MOVE里的代码:

MotionEvent.ACTION_MOVE -> {
    //移动控件
    moveItem(e)
    //更新点击的横坐标
    mLastX = e.x
    //拦截事件
    return true
}

这里返回了true?拦截事件?那后续的一系列事件不就是被当前view拦截了吗?果然仅仅一个onInterceptTouchEvent是搞不定的啊!

其实这里还有一个隐藏问题,computeScroll里面真的写对了吗?scrollBy和scrollTo有了解吗?

下面看再次改进的代码,主要就是改的上面两点,改动篇幅有点大,就全贴出来了。

编写代码III

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //流畅滑动
    private var mScroller = Scroller(context)
    //当前选中item
    private var mItem: ViewGroup? = null
    //上次按下横坐标
    private var mLastX = 0f
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when(e.action) {
               MotionEvent.ACTION_DOWN -> {
                   //获取点击位置
                   getSelectItem(e)
                   //设置点击的横坐标
                   mLastX = e.x
               }
               MotionEvent.ACTION_MOVE -> {
                   //判断是否拦截
                   return moveItem(e)
               }
//               MotionEvent.ACTION_UP -> {
//                   //判断结果
//                   stopMove()
//               }
           }
        }
        return super.onInterceptTouchEvent(e)
    }
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when(e.action) {
                //拦截了ACTION_MOVE后,后面一系列event都会交到本view处理
                MotionEvent.ACTION_MOVE -> {
                    //移动控件
                    moveItem(e)
                    //更新点击的横坐标
                    mLastX = e.x
                }
                MotionEvent.ACTION_UP -> {
                    //判断结果
                    stopMove()
                }
            }
        }
        return super.onTouchEvent(e)
    }
    //滑动结束
    //版本一:判断一下结束的位置,补充或恢复位置
    //问题:mLast不应该是down的位置
    //版本二:改进结果判断
    //问题:onInterceptTouchEvent的ACTION_UP不触发
    //版本三:改进补充或恢复位置的逻辑
    private fun stopMove() {
        mItem?.let {
            //如果移动过半了,应该判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if (abs(it.scrollX) >= deleteWidth / 2f) {
                //触发移动至完全展开
                mScroller.startScroll(it.scrollX, 0, deleteWidth - it.scrollX,0)
                invalidate()
            }else {
                //如果移动没过半应该恢复状态
                mScroller.startScroll(it.scrollX, 0, -it.scrollX,0)
                invalidate()
            }
            //清除状态
            mLastX = 0f
            //不能为null,后续流畅滑动要用到
            //mItem = null
        }
    }
    //移动item
    //版本一:绝对值小于删除按钮长度随便移动,大于则不移动
    //问题:移动方向反了,而且左右可以滑动,没有限定住范围,mLast只是记住down的位置
    //版本二:通过整体移动的数值,和每次更新的数值,判断是否在范围内,再移动
    //问题:onInterceptTouchEvent的ACTION_MOVE只触发一次
    //版本三:放在onTouchEvent内执行,并且在onInterceptTouchEvent给出一个拦截判断
    private fun moveItem(e: MotionEvent): Boolean {
        mItem?.let {
            val dx = mLastX - e.x
            //检查mItem移动后应该在[-deleteLength, 0]内
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            if ((it.scrollX   dx) <= deleteWidth && (it.scrollX   dx) >= 0) {
                //触发移动
                it.scrollBy(dx.toInt(), 0)
                return true
            }
        }
        return false
    }
    //获取点击位置
    //版本一:通过点击的y坐标除于item高度得出
    //问题:没考虑列表项的可见性、列表滑动的情况,并且x和屏幕有关不仅仅是列表
    //版本二:通过遍历子view检查事件在哪个view内,得到点击的item
    //问题:没有问题,成功拿到了mItem
    private fun getSelectItem(e: MotionEvent) {
        //获得第一个可见的item的position
        val frame = Rect()
        //防止点击其他地方,保持上一个item
        mItem = null
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }
    //流畅地滑动
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollTo(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

把上面代码运行下,果然就十分完美了,可是是不是觉得没彻底搞定啊?别急下面我们再加点东西.

优化

优化一:TouchSlop

TouchSlop是一个移动的最小距离,由系统提供,可以用它来判断一个滑动距离是否有效。

优化二:VelocityTracker

VelocityTracker是一个速度计算的工具,由native提供,可以计算移动像素点的速度,我们可以利用它判断当滑动速度很快时也展开删除按钮。

优化三:GestureDetector

GestureDetector是手势控制类,可以很方便的判断各种手势,我们这可以设计它双击展开删除按钮。

优化后代码

class SlideDeleteRecyclerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {
    //系统最小移动距离
    private val mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
    //最小有效速度
    private val mMinVelocity = 600
    //增加手势控制,双击快速完成侧滑,还是为了练习
    private var isDoubleClick = false
    private var mGestureDetector: GestureDetector
        = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener(){
            override fun onDoubleTap(e: MotionEvent?): Boolean {
                e?.let { event->
                    getSelectItem(event)
                    mItem?.let {
                        val deleteWidth = it.getChildAt(it.childCount - 1).width
                        //触发移动至完全展开deleteWidth
                        if (it.scrollX == 0) {
                            mScroller.startScroll(0, 0, deleteWidth, 0)
                        }else {
                            mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
                        }
                        isDoubleClick = true
                        invalidate()
                        return true
                    }
                }
                //不进行拦截,只是作为工具判断下双击
                return false
            }
        })
    //使用速度控制器,增加侧滑速度判定滑动成功,主要为了是练习
    //VelocityTracker 由 native 实现,需要及时释放内存
    private var mVelocityTracker: VelocityTracker? = null
    //流畅滑动
    private var mScroller = Scroller(context)
    //当前选中item
    private var mItem: ViewGroup? = null
    //上次事件的横坐标
    private var mLastX = 0f
    //当前RecyclerView被上层viewGroup分发到事件,所有事件都会通过dispatchTouchEvent给到
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        //
        mGestureDetector.onTouchEvent(ev)
        return super.dispatchTouchEvent(ev)
    }
    //viewGroup对子控件的事件拦截,一旦拦截,后续事件序列不会再调用onInterceptTouchEvent
    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when (e.action) {
                MotionEvent.ACTION_DOWN -> {
                    //这里的优化会阻止双击滑动的使用,实际也没什么好优化的
//                    //防止快速按下情况出问题
//                    if (!mScroller.isFinished) {
//                        mScroller.abortAnimation()
//                    }
                    //获取点击位置
                    getSelectItem(e)
                    //设置点击的横坐标
                    mLastX = e.x
                }
                MotionEvent.ACTION_MOVE -> {
                    //判断是否拦截
                    //如果拦截了ACTION_MOVE,后续事件就不触发onInterceptTouchEvent了
                    return moveItem(e)
                }
                //拦截了ACTION_MOVE,ACTION_UP也不会触发
//                MotionEvent.ACTION_UP -> {
//                    //判断结果
//                    stopMove()
//                }
            }
        }
        return super.onInterceptTouchEvent(e)
    }
    //拦截后对事件的处理,或者子控件不处理,返回到父控件处理,在onTouch之后,在onClick之前
    //如果不消耗,则在同一事件序列中,当前View无法再次接受事件
    //performClick会被onTouchEvent拦截,我们这不需要点击,全都交给super实现去了
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(e: MotionEvent?): Boolean {
        e?.let {
            when (e.action) {
                //没有拦截,也不能拦截,所以不需要处理
//                MotionEvent.ACTION_DOWN -> {}
                //拦截了ACTION_MOVE后,后面一系列event都会交到本view处理
                MotionEvent.ACTION_MOVE -> {
                    //移动控件
                    moveItem(e)
                    //更新点击的横坐标
                    mLastX = e.x
                }
                MotionEvent.ACTION_UP -> {
                    //判断结果
                    stopMove()
                }
            }
        }
        return super.onTouchEvent(e)
    }
    //滑动结束
    //版本一:判断一下结束的位置,补充或恢复位置
    //问题:mLast不应该是down的位置
    //版本二:改进结果判断
    //问题:onInterceptTouchEvent的ACTION_UP不触发
    //版本三:改进补充或恢复位置的逻辑
    private fun stopMove() {
        mItem?.let {
            //如果移动过半了,应该判定左滑成功
            val deleteWidth = it.getChildAt(it.childCount - 1).width
            //如果整个移动过程速度大于600,也判定滑动成功
            //注意如果没有拦截ACTION_MOVE,mVelocityTracker是没有初始化的
            var velocity = 0f
            mVelocityTracker?.let { tracker ->
                tracker.computeCurrentVelocity(1000)
                velocity = tracker.xVelocity
            }
            //判断结束情况,移动过半或者向左速度很快都展开
            if ( (abs(it.scrollX) >= deleteWidth / 2f) || (velocity < - mMinVelocity) ) {
                //触发移动至完全展开
                mScroller.startScroll(it.scrollX, 0, deleteWidth - it.scrollX, 0)
                invalidate()
            }else {
                //如果移动没过半应该恢复状态,或者向右移动很快则恢复到原来状态
                mScroller.startScroll(it.scrollX, 0, -it.scrollX, 0)
                invalidate()
            }
            //清除状态
            mLastX = 0f
            //不能为null,后续mScroller要用到
            //mItem = null
            //mVelocityTracker由native实现,需要及时释放
            mVelocityTracker?.apply {
                clear()
                recycle()
            }
            mVelocityTracker = null
        }
    }
    //移动item
    //版本一:绝对值小于删除按钮长度随便移动,大于则不移动
    //问题:移动方向反了,而且左右可以滑动,没有限定住范围,mLast只是记住down的位置
    //版本二:通过整体移动的数值,和每次更新的数值,判断是否在范围内,再移动
    //问题:onInterceptTouchEvent的ACTION_MOVE只触发一次
    //版本三:放在onTouchEvent内执行,并且在onInterceptTouchEvent给出一个拦截判断
    @SuppressLint("Recycle")
    private fun moveItem(e: MotionEvent): Boolean {
        mItem?.let {
            val dx = mLastX - e.x
            //最小的移动距离应该舍弃,onInterceptTouchEvent不拦截,onTouchEvent内才更新mLastX
            if(abs(dx) > mTouchSlop) {
                //检查mItem移动后应该在[-deleteLength, 0]内
                val deleteWidth = it.getChildAt(it.childCount - 1).width
                if ((it.scrollX   dx) <= deleteWidth && (it.scrollX   dx) >= 0) {
                    //触发移动
                    it.scrollBy(dx.toInt(), 0)
                    //触发速度计算
                    //这里Recycle不存在问题,一旦返回true,就会拦截事件,就会到达ACTION_UP去回收
                    mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                    mVelocityTracker!!.addMovement(e)
                    return true
                }
            }
        }
        return false
    }
    //获取点击位置
    //版本一:通过点击的y坐标除于item高度得出
    //问题:没考虑列表项的可见性、列表滑动的情况,并且x和屏幕有关不仅仅是列表
    //版本二:通过遍历子view检查事件在哪个view内,得到点击的item
    //问题:没有问题,成功拿到了mItem
    private fun getSelectItem(e: MotionEvent) {
        //获得第一个可见的item的position
        val frame = Rect()
        //防止点击其他地方,保持上一个item
        mItem = null
        forEach {
            if (it.visibility != GONE) {
                it.getHitRect(frame)
                if (frame.contains(e.x.toInt(), e.y.toInt())) {
                    mItem = it as ViewGroup
                }
            }
        }
    }
    //流畅地滑动
    override fun computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mItem?.scrollTo(mScroller.currX, mScroller.currY)
            postInvalidate()
        }
    }
}

TouchSlop、VelocityTracker和GestureDetector的用法都很简单,但是有一点必须得说一下,那就是在dispatchTouchEvent中传递事件给GestureDetector,为什么呢?因为onInterceptTouchEvent拦截后就搜不到事件了,onTouchEvent的执行和自身及子控件有关,有不确定性,只有dispatchTouchEvent中的事件一定会收到!

总结

一篇文章下来,代码贴的有点多了,篇幅很长,但是如果仔细品的话,你会发现从事件分发到拦截都从问题里面学到了,几种滑动方式以及滑动的相对性也涉及了,坐标系也有了一定理解,其他几个工具TouchSlop、VelocityTracker和GestureDetector都用到了,还算可以吧!

到此这篇关于Android自定义view实现左滑删除的RecyclerView详解的文章就介绍到这了,更多相关Android RecyclerView内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Android自定义view实现左滑删除的RecyclerView详解的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部