自定义布局:西部世界 第一季

自定义布局:西部世界 第一季

这个自定义布局要求显示为 系列名称... + 第一季 ,后面的季内容显示完全,紧贴系列名称显示,系列名称在布局不允许的时候可以部分显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* 系列名称... + 第一季
* 后面的季内容显示完全,紧贴系列名称显示,系列名称在布局不允许的时候可以部分显示
*/
class FixedEndLinearLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//获取父布局测量size和model
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
if (childCount != 2) throw RuntimeException("FixedEndLinearLayout must have 2 children.")
val wrapChild = getChildAt(0)
val fixedChild = getChildAt(1)

//测量
measureChild(fixedChild, widthMeasureSpec, heightMeasureSpec)
val fixedParams = fixedChild.layoutParams as MarginLayoutParams

val fixedChildWidth =
fixedChild.measuredWidth + fixedParams.leftMargin + fixedParams.rightMargin
val fixedChildHeight =
fixedChild.measuredHeight + fixedParams.topMargin + fixedParams.bottomMargin

val wrapChildWidthSpec = ViewGroup.getChildMeasureSpec(
widthMeasureSpec,
paddingLeft + paddingRight + fixedChildWidth, wrapChild.layoutParams.width
)
val wrapChildHeightSpec = ViewGroup.getChildMeasureSpec(
heightMeasureSpec, paddingTop + paddingBottom, wrapChild.layoutParams.height
)
wrapChild.measure(wrapChildWidthSpec, wrapChildHeightSpec)

val wrapParams = wrapChild.layoutParams as MarginLayoutParams

val wrapChildWidth =
wrapChild.measuredWidth + wrapParams.leftMargin + wrapParams.rightMargin
val wrapChildHeight =
wrapChild.measuredHeight + wrapParams.topMargin + wrapParams.bottomMargin

val width = wrapChildWidth + fixedChildWidth
val height = fixedChildHeight.coerceAtLeast(wrapChildHeight)

start0 = paddingLeft + wrapParams.leftMargin
start1 = paddingLeft + wrapChildWidth + fixedParams.leftMargin

setMeasuredDimension(
if (widthMode == MeasureSpec.EXACTLY) widthSize else width + paddingLeft + paddingRight,
if (heightMode == MeasureSpec.EXACTLY) heightSize else height + paddingTop + paddingBottom
)

}

private var start0 = 0
private var start1 = 0

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
val wrapChild = getChildAt(0)
val fixedChild = getChildAt(1)
val y0 = (measuredHeight - wrapChild.measuredHeight) / 2
val y1 = (measuredHeight - fixedChild.measuredHeight) / 2
wrapChild.layout(
start0,
y0,
start0 + wrapChild.measuredWidth,
y0 + wrapChild.measuredHeight
)
fixedChild.layout(
start1,
y1,
start1 + fixedChild.measuredWidth,
y1 + fixedChild.measuredHeight
)
}
}

哲学启蒙

哲学启蒙

人们的烦恼,基本是源于读书太少,而想得太多。

读哲学,不只是去吸收大家们的思想,而是在读的同时不要忘记思考。

哲学也许给不了人成功,也不一定会带来富有,但至少获得了自由。

大问题

作者: [美国] 罗伯特·C. 所罗门

哲学的故事

作者:威尔·杜兰特

用讲故事的方法,介绍了主要哲学家的生平及其观点,从苏格拉底、柏拉图、亚里士多德到叔本华、尼采再到柏格森,罗素、杜威等。在阐述每位哲学家思想的同时,生动地介绍了他们生活的时代背景、生活境遇和情感经历。

苏菲的世界

作者:乔斯坦·贾德

14岁的少女苏菲不断接到一些极不寻常的来信,世界像谜团一般在她眼底展开,她在一位神秘导师的指引下,运用少女天生的悟性与后天知识,企图解开这些谜团,然而。事实真相远比她所想的更怪异、更离奇……

西方哲学简史

作者:伯特兰·罗素

记述了从西方哲学萌芽的古希腊哲学一直到二十世纪早期期西方哲学的发展历程。

《西方哲学简史》是在罗素的代表作《西方哲学史》的基础上,保留原著架构,并对一些繁复的逻辑论证进行通俗化的基础上编译而成,使其更加适合普通读者阅读。

禅与摩托车维修艺术

作者:罗伯特·M.波西格

1968年,他与长子克里斯一起骑着摩托车从双子城出发,在中西部旷野、落基山区和西海岸从事心灵探险。他之所以开始这场横跨美国大陆的万里长旅,是希望从狭窄而受限的自我解脱。一路经过复杂经验与反省思考,他终于找到生理上的、精神上的完整与清净。

霍金高评:

时间简史

霍金

你的第一本哲学书

作者:托马斯·内格尔

Terminal设置代理

0x01 Terminal代理设置

给终端terminal设置代理

1
2
3
4
5
$ vim ~/.zshrc
## proxy
export http_proxy=http://127.0.0.1:8001
export https_proxy=$http_proxy
$ source ~/.zshrc

0x02 AndroidStudio代理设置

Android Studio 的代理设置

Perference --> Appearance & Behavior --> System Settings --> Http Proxy

20230517195623

0x03 Gradle代理设置

gradle代理可以配置在:

  1. 环境变量GRADLE_USER_HOME指定的gradle系统目录默认路径\Users\Xxx\.gradle\gradle.properties
  2. 项目根目录gradle.properties

前者优先级更高。

1
2
3
4
5
6
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=1080
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=1080
systemProp.socks.proxyHost=127.0.0.1
systemProp.socks.proxyPort=1080

0x04 配置代理用户名和密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Project-wide Gradle settings.
...
systemProp.http.proxyHost=proxy.company.com
systemProp.http.proxyPort=443
systemProp.http.proxyUser=username
systemProp.http.proxyPassword=password
systemProp.http.auth.ntlm.domain=domain

systemProp.https.proxyHost=proxy.company.com
systemProp.https.proxyPort=443
systemProp.https.proxyUser=username
systemProp.https.proxyPassword=password
systemProp.https.auth.ntlm.domain=domain
...

0x05 过滤代理

对于国内的仓库可以不走代理,还有部分内网地址也可以不走代理

1
2
3
systemProp.http.nonProxyHosts=developer.huawei.com|maven.aliyun.com|192.168.*
systemProp.https.nonProxyHosts=developer.huawei.com|maven.aliyun.com|192.168.*
systemProp.socks.nonProxyHosts=developer.huawei.com|maven.aliyun.com|192.168.*

0x06 修改gradle.properties配置

1
org.gradle.jvmargs=-DsocksProxyHost=127.0.0.1 -DsocksProxyPort=10808

Android专栏-BaseQuickAdapterHelper

Android专栏-BaseQuickAdapterHelper

0x01 自动加载更多-LoadingFooterView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import android.view.View
import android.view.ViewGroup
import com.chad.library.adapter.base.loadmore.BaseLoadMoreView
import com.chad.library.adapter.base.loadmore.LoadMoreStatus
import com.chad.library.adapter.base.util.getItemView
import com.chad.library.adapter.base.viewholder.BaseViewHolder

class LoadingFooterView : BaseLoadMoreView() {
private var loadingView: LoadingPagView? = null
override fun getRootView(parent: ViewGroup): View {
val rootView = parent.getItemView(R.layout.ui_footer_adapter_load_more)
loadingView = rootView.findViewById(R.id.loadingView)
return rootView
}

override fun getLoadingView(holder: BaseViewHolder): View {
return holder.getView(R.id.loadingView)
}

override fun getLoadComplete(holder: BaseViewHolder): View {
return holder.getView(R.id.fakeView)
}

override fun getLoadEndView(holder: BaseViewHolder): View {
return holder.getView(R.id.endView)
}

override fun getLoadFailView(holder: BaseViewHolder): View {
return holder.getView(R.id.fakeView)
}

override fun convert(holder: BaseViewHolder, position: Int, loadMoreStatus: LoadMoreStatus) {
super.convert(holder, position, loadMoreStatus)
when (loadMoreStatus) {
LoadMoreStatus.Complete -> {
loadingView?.stopPlay()
}
LoadMoreStatus.Loading -> {
loadingView?.startPlay()
}
LoadMoreStatus.Fail -> {

}
LoadMoreStatus.End -> {
loadingView?.stopPlay()
}
}
}
}

VolumeDialog音量控制自定义

VolumeDialog音量控制自定义

1 使用自定义 AlertDialog 实现

2 window?.setFlags 设置 dialog 的样式

3 window?.attributes 设置 dialog 的位置

4 返回键监听,兼容机型需要使用 setOnKeyListener

5 按一次音量键回调多次的问题,KeyEvent.action 事件分 KeyEvent.ACTION_UP 和 KeyEvent.ACTION_DOWN

6 音量加减需要获取系统音量 max 值来手动控制,不同手机 max 值域不同

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import android.app.Activity
import android.content.Context
import android.content.res.Resources
import android.media.AudioManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.KeyEvent
import android.view.WindowManager
import android.widget.ProgressBar
import androidx.appcompat.app.AlertDialog

/**
* 声音调整控件
*/
class VolumeDialog(activity: Activity) : AlertDialog(activity, R.style.VolumeDialog) {
companion object {
private const val TAG = "VolumeDialog"

@JvmStatic
fun show(activity: Activity) {
activity.let {
if (activity.isFinishing) return
VolumeDialog(activity).show()
}
}
}

private val volumeAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private var volume = volumeAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
private val maxVolume = volumeAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
private val step = (maxVolume / 10).coerceAtLeast(1)
private val delayMillis = 1500L

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_volume_progress)

//实现Dialog区域外部事件可以传给Activity
// FLAG_NOT_TOUCH_MODAL作用:即使该window可获得焦点情况下,仍把该window之外的任何event发送到该window之后的其他window
window?.setFlags(
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
)

// FLAG_WATCH_OUTSIDE_TOUCH作用:如果点击事件发生在window之外,就会收到一个特殊的MotionEvent,为ACTION_OUTSIDE
window?.setFlags(
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
)

// 顶部显示
// window?.setGravity(Gravity.TOP)
val attrs = window?.attributes
attrs?.apply {
gravity = Gravity.TOP
height = WindowManager.LayoutParams.WRAP_CONTENT
width = WindowManager.LayoutParams.MATCH_PARENT
y = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
35f,
Resources.getSystem().displayMetrics
).toInt()
}
Log.d(TAG, "onCreate() called with: attrs = $attrs")
window?.attributes = attrs
// 按空白处不能取消
setCanceledOnTouchOutside(false)
// 初始化界面控件
initView()
}

private var volumeProgressView: VolumeProgressView? = null
private var progressBar: ProgressBar? = null

private val _handler = Handler(Looper.getMainLooper())
private val r = Runnable {
try {
dismiss()
} catch (e: Exception) {
// sometimes happens windows token error.
e.printStackTrace()
}
}

override fun dismiss() {
_handler.removeCallbacks(r)
super.dismiss()
}

private fun initView() {
Log.d(TAG, "initView: ")
volumeProgressView = findViewById(R.id.vpv_volume)
progressBar = findViewById(R.id.pb_volume)

refreshProgress(volume * 1f / maxVolume)

// 解决不同机型版本兼容问题,onKeyDown 可能被拦截
setOnKeyListener { _, keyCode, event ->
Log.d(TAG, "setOnKeyListener: keyCode = $keyCode, event = $event")
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
handleKeyEvent(keyCode, event)
true
} else {
false
}
}
}

private fun refreshProgress(volumePercent: Float) {
_handler.removeCallbacks(r)
volumeProgressView?.setProgress(volumePercent)
progressBar?.progress = (volumePercent * 1000).toInt()
_handler.postDelayed(r, delayMillis)
}


private fun handleKeyEvent(keyCode: Int, event: KeyEvent): Boolean {
Log.d(TAG, "handleKeyEvent: volume = $volume, maxVolume = $maxVolume, step = $step")
if ((keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN) || event.action != KeyEvent.ACTION_DOWN) return false
volume = if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // up
if (volume == maxVolume) return true
maxVolume.coerceAtMost(volume + step)
} else { // down
if (volume == 0) return true
0.coerceAtLeast(volume - step)
}
val volumePercent = volume * 1f / maxVolume
refreshProgress(volumePercent)

// 变更声音
volumeAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0)
Log.d(TAG, "handleKeyEvent: AudioManager set volume = $volume done.")

return true
}

/**
* 返回事件,仅拦截音量控制事件
*/
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
Log.d(TAG, "onKeyDown() called with: keyCode = $keyCode, event = $event")
return if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
handleKeyEvent(keyCode, event)
return true
} else {
super.onKeyDown(keyCode, event)
}
}
}

自定义Dialog的Style

1
2
3
4
5
6
7
8
9
10
11
12
 <style name="VolumeDialog" parent="android:style/Theme.Dialog">
<!--背景颜色及和透明程度-->
<item name="android:windowBackground">@android:color/transparent</item>
<!--是否去除标题 -->
<item name="android:windowNoTitle">true</item>
<!--是否去除边框-->
<item name="android:windowFrame">@null</item>
<!--是否浮现在activity之上-->
<item name="android:windowIsFloating">true</item>
<!--是否模糊-->
<item name="android:backgroundDimEnabled">false</item>
</style>

观察者模式Kotlin泛型实现消息中心

观察者模式 + Kotlin 泛型实现的简易版消息中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class MessageCenter<T> {
companion object {
private val centers = mutableMapOf<String, Any>()
fun <T> getInstance(clazz: Class<T>): MessageCenter<T> {
return if (centers[clazz.simpleName] != null) {
centers[clazz.simpleName] as MessageCenter<T>
} else {
val messageCenter = MessageCenter<T>()
centers[clazz.simpleName] = messageCenter
messageCenter
}
}
}

fun register(observer: Observer<T>) {
observers.add(observer)
}

fun unregister(observer: Observer<T>) {
if (observers.contains(observer)) {
observers.remove(observer)
}
}

fun post(t: T) {
observers.forEach {
it.receive(t)
}
}

private var observers = mutableListOf<Observer<T>>()
}

interface Observer<T> {
fun receive(t: T)
}

fun main() {
val ob = object : Observer<String> {
override fun receive(t: String) {
println("result is: $t")
}
}
MessageCenter.getInstance(String::class.java).register(ob)
MessageCenter.getInstance(String::class.java).post("txt post.")
MessageCenter.getInstance(String::class.java).unregister(ob)

MessageCenter.getInstance(Int::class.java).post(123)
}
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×