ViewPager2的预加载问题
ViewPager2
的默认预加载数量为3,而在Tablayout
和ViewPager2
attach
后,如果一次点击的跨越度超过3
,就会出现莫名指针越界的异常,如果把预加载数量拉满当然可以解决这些问题,但是在实际应用中并不需要加载全部界面(可能会导致卡顿等问题),考虑使用懒加载解决(未实现)- 另外,
ViewPager2
的预加载无法禁止,在源码中Google
的说明:
Set the number of pages that should be retained to either side of the currently visible page(s).
Pages beyond this limit will be recreated from the adapter when needed.
Set this to OFFSCREEN_PAGE_LIMIT_DEFAULT to use RecyclerView's caching strategy. The given value must either be larger than 0, or #OFFSCREEN_PAGE_LIMIT_DEFAULT.
Pages within limit pages away from the current page are created and added to the view hierarchy,even though they are not visible on the screen.
Pages outside this limit will be removed from the view hierarchy, but the ViewHolders will be recycled as usual by RecyclerView.
This is offered as an optimization. If you know in advance the number of pages you will need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting can have benefits in perceived smoothness of paging animations and interaction.
If you have a small number of pages (3-4) that you can keep active all at once, less time will be spent in layout for newly created view subtrees as the user pages back and forth.
You should keep this limit low, especially if your pages have complex layouts. By default it is set toOFFSCREEN_PAGE_LIMIT_DEFAULT.
Params:
limit – How many pages will be kept offscreen on either side. Valid values are all values >= 1 and OFFSCREEN_PAGE_LIMIT_DEFAULT
Throws:
IllegalArgumentException – If the given limit is invalid
ViewPager2 嵌套滑动矛盾
ViewPager2
内部的滑动控件与ViewPager2
滑动方向相同时,滑动事件会被ViewPager2
优先消费,如果需要实现嵌套滑动的效果,需要额外编写逻辑- 谷歌官方给的解决办法是在外部的
ViewPager2
和内部的ViewPager2
间添加一个容器
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.viewpager2.integration.testapp
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlin.math.absoluteValue
import kotlin.math.sign
/**
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
*
* This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
*/
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
private val parentViewPager: ViewPager2?
get() {
var v: View? = parent as? View
while (v != null && v !is ViewPager2) {
v = v.parent as? View
}
return v as? ViewPager2
}
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false
1 -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return
// Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}
- 使用时直接在
xml
中的ViewPager2
外部作为一个ViewGroup
使用即可
ViewPager2 切换fragment高度不一致
ViewPager2
如果使用默认设置,会选择刚开始传入的fragmentList
的第一个fragment
作为其它fragment
的高度,如果其中的fragment
含有如NestedScrollView
等滑动控件,会因高度固定而无法滑动- 解决办法:每次切换时动态测量高度,我把对切换时动态测量的函数写在了一个单例类中,在声明时(
mediator
绑定前)进行注册即可 Activity
中
val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
val viewPager2 = findViewById<ViewPager2>(R.id.viewPager2)
viewPager2.adapter = ViewPager2Adapter(supportFragmentManager,lifecycle,fragmentList)
viewPager2.isUserInputEnabled = false
/*
这里对整体进行注册
*/
Tools.updateViewPager2(viewPager2,fragmentList)
val mediator = TabLayoutMediator(tabLayout,viewPager2,false,true){
tab: TabLayout.Tab, i: Int ->
tab.text = textList[i]
}
mediator.attach()
- 注册回调
class Tools {
companion object{
public fun updateViewPager2(viewPager2: ViewPager2,fragmentList:ArrayList<Fragment>){
viewPager2.registerOnPageChangeCallback(
object:ViewPager2.OnPageChangeCallback() {
//重写ViewPager2.OnPageChangeCallback()中的onPageSelected
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
//这个view是当前被选中的fragment。
val view = fragmentList[position].view
view?.let {
updatePagerHeightForChild(view,viewPager2)
}
}
}
)
viewPager2.viewTreeObserver.addOnGlobalLayoutListener {
val view = fragmentList[viewPager2.currentItem].view
view?.let {
updatePagerHeightForChild(view,viewPager2)
}
}
}
//解决viewpager2高度问题
public fun updatePagerHeightForChild(view: View, pager: ViewPager2) {
val wMeasureSpec =
View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY)
val hMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
view.measure(wMeasureSpec, hMeasureSpec)
if (pager.layoutParams.height != view.measuredHeight) {
val lp : ViewGroup.LayoutParams = pager.layoutParams
lp.height = view.measuredHeight
pager.layoutParams = lp
}
}
}
}
Button的背景设置
- 如果对Button的背景设置无反应,看看是不是theme的设置问题。我的做法是把主题设定为
Theme.MaterialComponents.NoActionBar.Bridge
UI更新
Android
关于UI
的更新最好都使用Google
推荐的Handler
方式,使用runUiOnThread
可能会出现诡异异常
Gradle
Gradle
的报错有可能与代理设置相关,可以在gradle.properties
文件中查看是否有设置代理,如果有删除即可- 项目的
gradle
和本地已经安装的gradle
都要检查一遍,Android Studio
设置代理时可以直接一键在proxy
相关中设置,但是如果选择不使用代理,Android Studio
不会帮你删除,还有可能一直使用
Fragment
- 在最新的标准中
onActivityCreated()
已经被弃用,Google推荐把控件初始化的相关逻辑写在onViewCreated
中
RecyclerView
- 更新数据时如果使用
notifyDataSetChange()
只会展现一个一闪而过的动画,如果希望动画更加流畅或不影响整体性能,最好还是使用部分更新的notifyItemInserted()
系列方法 - 默认的主题在过度滑动时会出现一个难看的回弹,可以在xml文件中把
android:overScrollMode
设定为never
startActivityForResult
- 该方法及与其相匹配的
onActivityResult
已经被弃用,官方推荐使用registerForActivityResult
- 此方法在
Activity
和Fragment
中都可以直接声明,建议在外部声明(如果在函数内声明,涉及生命周期的问题,即需要在onCreate
后注册) - 声明:
private val startActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
if(it.data!=null&&it.resultCode==Activity.RESULT_OK){
}
}
- 使用:
val intent = Intent(this,targetActivity:class.java)
startActivity.lauch(intent)
- 记得在
targetActivity
中设置返回结果setResult(Activity.RESULT_OK)
,不要把结果设置在onDestory()
中,否则会设置无效
Idea启动弹窗报错
- 可能因为正常运行时电脑突然断电或其它意外因素造成的突然退出,这时候
Idea
的网络端口会一直处于占用状态,在cmd
管理者模式中使用net stop winnat
,停止服务,再开启服务net stop winnat
即可解决
YCCustomText
- 使用时发现图片无法加载,后查看源码发现项目使用管理者模式,需要手动在使用中设置
ImageLoader
- 示范:
HyperManager.getInstance().setImageLoader(NotesImageLoader())
Support库兼容问题
- 较早的库一般使用
support.v7
系列库,在导入其它人写的库时注意实现方式,如果外来库使用该包实现,要在Project
下的gradle.properties
中加入说明,我的文件如下所示
#
# @Copyright 2023 EricMoin
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.enableJetifier=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
- 关键处为:
android.enableJetifier=true
提供support
库支持android.useAndroidX=true
提供androidx
库支持
Import Module
- 在把自己的项目作为
module
与原项目进行合并时,注意AndroidManifest.xml
和build.gradle
的配置 - 具体操作
Missing Default
- 报错描述:
The layout "xx" in layout has no declaration
in the base layout folder; this can lead to
crashes when the resource is queried in a configuration
that does not match this qualifier
Android Studio
莫名bug
,重启解决
TabLayout
- 字体大小有限制,如果
xml
中app:tabMode="fixed"
,那么就会默认均分视图,此时字体的大小有最大大小,同时,在屏幕较窄的机型中如果已经固定字体大小,会出现文字竖排的情况,这时候考虑删减标签或者把tab
改为滑动模式 app:tabRippleColor="@android:color/transparent"
该属性可与点击后的波动效果相关,把颜色设为透明即可取消波动效果
方法数过多无法打包apk
- 一般
apk
在打包时会有方法数限制 - 报错显示:
Cannot fit requested classes in a single dex file (# methods: 73138 > 65536)
- 在
app
下的build.gradle
文件中使用multidex
库
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.app'
compileSdk 33
defaultConfig {
minSdk 19
targetSdk 33
/*
在这里启用multiDex
*/
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
apply plugin: 'kotlin-kapt'
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
/*
引入multidex库
*/
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.google.android.material:material:1.8.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
LiveData
- 原属于
Transformations
的swichMap
和map
已成为livedata
的内置函数,在使用时直接进行引用即可,如:
val weatherLiveData = locationLiveData.switchMap {
location -> WeatherRepository.refreshWeather(location.lng,location.lat)
}
获取时间
- 没有使用最新的方法,这里记录
Calender
版本
private fun getTime():Time {
val calendar = Calendar.getInstance()
return Time(
(calendar.get(java.util.Calendar.YEAR)+1).toString(),
(calendar.get(java.util.Calendar.MONTH)).toString(),
(calendar.get(java.util.Calendar.DAY_OF_MONTH)).toString(),
(calendar.get(java.util.Calendar.HOUR_OF_DAY)).toString(),
(calendar.get(java.util.Calendar.MINUTE)).toString()
)
}
Android沉浸式状态栏
- 在
xml
中使用属性android:fitsSystemWindows="true"
- 在
Activity
或Fragment
中,对状态栏进行设置
val decorView = window.decorView
decorView.systemUiVisibility =View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.statusBarColor = Color.TRANSPARENT
- 但在最新的标准中这种方法已经被弃用,更被推荐的写法是使用:
WindowInsetsController
- 更多详情:https://blog.csdn.net/qq_32664007/article/details/126279919
LifeCycleObServer
- 为了能观察一个
Activity
的生命周期,Google
官方在JetPack
中推出了LifeCycle
使得我们能够更方便的通过观察者模式获取生命周期,在《第一行代码(第三版)》中,给出了这样一个简单的观察demo
class MainActivity:AppCompatActivity{
...
override fun onCreate(savedInstanceState:Bundle?){
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
/*
AppCompatActivity就是一个lifecycleOwner,在
设置观察者的时候可以主动把自己传入来使得Observer
可以主动获取当前的生命周期
*/
lifecycle.addObserver(MyObserver(this))
}
...
}
class MyObserver(val lifecycle:Lifecycle):LifecycleObserver{
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart(){
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop(){
...
}
}
- 而在最新的
lifecycle
中,@OnLifecycleEvent
注解已经被废弃,新的实现方式为:
class MyObserver():DefaultLifecycleObserver{
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
}
}
- 新的继承改为了
DefaultLifecycleObserver
,要想获取相应的生命周期,直接重写该方法即可 - 官网地址:https://developer.android.google.cn/jetpack/androidx/releases/lifecycle?hl=zh-tw
项目导入处理
-
在本地运行一个外来项目时,常常会出现配置不一致而导致的“水土不服”问题,此处记录导入项目时需要配置的设置
-
查看项目所用
gradle
版本
-
查看
gradle
编译所用JDK
版本
-
查看编译相关工具版本
Dialog返回信息给Activity
- 使用监听,在
Dialog
中保存监听引用,由Activity
传入实现的Listener
,每次更改都能被监听到
/*
* @Copyright 2023 EricMoin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.notes.card
import com.example.notes.card.TimeCard
interface TimeCallBackListener {
public fun onTimeCallBack(fromTime: TimeCard, toTime: TimeCard)
}
/*
* @Copyright 2023 EricMoin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.notes.card
import android.text.format.Time
import java.util.*
class TimeCard(
val notesYear:String,
val notesMonth:String,
val notesDay:String,
val notesHour:String,
val notesMinute:String
) {
companion object{
const val HYPHEN = "-"
const val COLON = ":"
const val SPACE = " "
const val TO = "至"
const val YEAR = "年"
const val MONTH = "月"
const val DAY = "日"
}
}
val dialog = DatePickerDialog(notesActivity, com.google.android.material.R.style.Theme_Material3_DayNight_Dialog)
dialog.setTimeListener(
object : TimeCallBackListener {
override fun onTimeCallBack(fromTime: TimeCard, toTime: TimeCard) {
val datesRange = StringBuilder()
.append(fromTime.notesYear)
.append(TimeCard.HYPHEN)
.append(fromTime.notesMonth)
.append(TimeCard.TO)
.append(toTime.notesYear)
.append(TimeCard.HYPHEN)
.append(toTime.notesMonth)
val notesDateRangeText = view.findViewById<TextView>(R.id.notesDateRangeText)
notesDateRangeText.text = datesRange.toString()
adapter.sortByTime(fromTime,toTime)
Log.d("NotesFragment","Time is updated")
}
}
)
/*
* @Copyright 2023 EricMoin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.notes.dialog
import android.app.Activity
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.*
import android.widget.TextView
import com.example.notes.R
import com.example.notes.util.TimeCallBackListener
import com.example.notes.card.TimeCard
import com.itheima.wheelpicker.WheelPicker
class DatePickerDialog(val mainActivity: Activity, themeResId: Int) : Dialog(mainActivity, themeResId) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.notes_wheel_picker)
setCancelable(true)
setCanceledOnTouchOutside(true)
window?.setGravity(Gravity.BOTTOM)
window?.setBackgroundDrawableResource(R.drawable.pop_panel_radius)
window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.WRAP_CONTENT)
initDataPicker()
}
private lateinit var dateYearList:ArrayList<String>
private lateinit var dateFromMonthList:ArrayList<String>
private lateinit var dateToMonthList:ArrayList<String>
private lateinit var dateYearPicker:WheelPicker
private lateinit var dateFromMonthPicker:WheelPicker
private lateinit var dateToMonthPicker:WheelPicker
/*
这里把外部传入的监听进行设置
*/
private lateinit var timeListener: TimeCallBackListener
public fun setTimeListener(timeListener: TimeCallBackListener){
this.timeListener = timeListener
}
private fun initDataPicker() {
dateYearList = ArrayList<String>()
dateFromMonthList = ArrayList<String>()
dateToMonthList = ArrayList<String>()
dateYearPicker = findViewById<WheelPicker>(R.id.dateYearPicker)
dateFromMonthPicker = findViewById<WheelPicker>(R.id.dateFromMonthPicker)
dateToMonthPicker = findViewById<WheelPicker>(R.id.dateToMonthPicker)
for(i in 1990..2023){
val str = StringBuilder()
str.append(i)
str.append(TimeCard.YEAR)
dateYearList.add( str.toString() )
}
for(i in 1..12){
val str = StringBuilder()
str.append(i)
str.append(TimeCard.MONTH)
dateFromMonthList.add( str.toString() )
dateToMonthList.add( str.toString() )
}
dateYearPicker.data = dateYearList
// dateYearPicker.selectedItemPosition = 2
dateFromMonthPicker.data = dateFromMonthList
// dateFromMonthPicker.selectedItemPosition = 2
dateToMonthPicker.data = dateToMonthList
// dateToMonthPicker.selectedItemPosition = 2
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (isOutOfBounds(context, event)) {
val fromTime = TimeCard(
dateYearList[dateYearPicker.currentItemPosition].replace(TimeCard.YEAR,""),
dateFromMonthList[dateFromMonthPicker.currentItemPosition].replace(TimeCard.MONTH,""),
"",
"",
""
)
val toTime = TimeCard(
dateYearList[dateYearPicker.currentItemPosition].replace(TimeCard.YEAR,""),
dateToMonthList[dateToMonthPicker.currentItemPosition].replace(TimeCard.MONTH,""),
"",
"",
""
)
/*
此处进行方法回调,每次更改都能被外部的Activity接收
*/
timeListener.onTimeCallBack(fromTime,toTime)
dismiss()
}
return super.onTouchEvent(event)
}
private fun isOutOfBounds(context: Context,event: MotionEvent):Boolean{
val x = event.x
val y = event.y
val slop = ViewConfiguration.get(context).scaledWindowTouchSlop
val decorView = window!!.decorView
return (x<-slop)||(y<-slop)||(x>(decorView.width+slop+slop))||(y>(decorView.width+slop+slop))
}
}