Android开发问题小结

ViewPager2的预加载问题

  • ViewPager2的默认预加载数量为3,而在TablayoutViewPager2 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:
IllegalArgumentExceptionIf 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
  • 此方法在ActivityFragment中都可以直接声明,建议在外部声明(如果在函数内声明,涉及生命周期的问题,即需要在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.xmlbuild.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

  • 字体大小有限制,如果xmlapp: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

  • 原属于TransformationsswichMapmap已成为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"
  • ActivityFragment中,对状态栏进行设置
val decorView = window.decorView
decorView.systemUiVisibility =View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window.statusBarColor  = Color.TRANSPARENT

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)
    }
}

项目导入处理

  • 在本地运行一个外来项目时,常常会出现配置不一致而导致的“水土不服”问题,此处记录导入项目时需要配置的设置

  • 查看项目所用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))
    }
}

Apache 2.0开源许可

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
声明:这些内容是逐步总结过来的,所以可能有当时的理解不正确,只希望大家能做个参考: 内容如下: 目录 一句话总结汇总: Copy project into workspace 和add project into work set 的含义 数字签名总结 JNI 基础及注意: Ndk的使用方法: Ant 与 android update project 命令行只有在非根盘符上才能运行 android Launcher Android 运行环境搭建 Android:name什么时候加”.” Activity class {package/class} does not exist 问题的解决 Activity 中两次调用OnCreate的原因 ByteBuffer 和 FloatBuffer 的直接分配和非直接分配 Application的使用小总结 “call to OpenGL ES API with no current context (logged once per thread” 问题的解决 2013年9月7日19:15:33:我的平板分辨率很高可是运行public void onSurfaceChanged(GL10 gl, int width, int height)函数时,分辨率只有 455*320,这是为什么? 关于android添加第三方字体的方法 android-apt-compiler: [t1] res\layout\LinearLayout.xml: Invalid file name: must contain only [a-z0-9_.] Buttons in button bars should be borderless android 支持的距离单位 使用adb shell命令进入手机后使用ls命令提示: opendir failed permission denied 使用adb pull 命令提示permission denied Button 中的setLayoutParams使用注意: layout文件夹和raw文件下面的文件读取 Matrix方法中的set方法和post方法 android 中调用drawBitmap时理解dip(屏幕密度)和px(像素)的区别 SQLiteDatabase 的setTransactionSuccessful作用 终于弄明白 paddingleft margineleft layout_gravity 和gravity之间的区别 自定义控件时要注意的问题。 obtainMessage 的作用: FrameLayout 需要注意的地方: EditText 禁止弹出按键盘: 获取控件屏幕位置和窗口位置: 为什么MyAdapater的getView没有被调用 XmlSerializer使用总结: ListView中的Item自定义点击后的背景色的方法。 drawable各个分辨率 fragment 的几种创建方式 fragment第一次使用遇到的问题 activity变身对话框 onMeasure 中的AT_MOST EXACTLY UNSPECIFIED MotionEvent的触发记录 对于Drawable 的 getIntrinsicHeight 和getIntrinsicWidth的理解 IntentService 使用总结: 文件读写总结: AES 解密失败: XML中的include标签加入后崩溃 Button的background标签使图像拉伸的问题 SharedPreferences 的getString 的陷阱 TextView 中的EMS和Maxlength

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值