From 1a5a756e8229b940e7053a8af3dc5cbe2701b99b Mon Sep 17 00:00:00 2001 From: Shuming Liu Date: Fri, 2 Oct 2026 11:02:36 +0800 Subject: [PATCH] relayout of train view --- .../oraltrainer/sample/AppConfig.kt | 1 - .../sample/CourseCatalogController.kt | 8 - .../oraltrainer/sample/MainActivity.kt | 71 -------- .../oraltrainer/sample/SampleScreenActions.kt | 3 - .../oraltrainer/sample/SampleScreenFactory.kt | 21 +-- .../oraltrainer/sample/ui/PlayerChrome.kt | 159 +++++++++++++----- .../oraltrainer/sample/ui/SampleWidgets.kt | 36 ---- .../oraltrainer/sample/ui/TrainScreen.kt | 97 +---------- 8 files changed, 129 insertions(+), 267 deletions(-) diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/AppConfig.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/AppConfig.kt index 0bb7d55..50f83cc 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/AppConfig.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/AppConfig.kt @@ -13,7 +13,6 @@ internal const val ASSESSMENT_API_KEY = internal const val SSO_LOGIN_URL = "https://d1kt.cn/login" -internal const val PICK_VIDEO_REQUEST = 1001 internal const val RECORD_AUDIO_REQUEST = 1002 internal const val PROGRESS_MAX = 1000 diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/CourseCatalogController.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/CourseCatalogController.kt index b267afd..6f7343f 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/CourseCatalogController.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/CourseCatalogController.kt @@ -167,14 +167,6 @@ internal class CourseCatalogController( } } - fun useCourseInPlace(video: TrainingVideoSummary) { - // Keep the current module (e.g. Train) while syncing course selection. - if (screenState.courseEnrollment[video.videoHash] != true) { - enrollCourse(video) - } - loadRemoteVideo(video) - } - fun loadRemoteVideo(video: TrainingVideoSummary) { screenState.currentCourse = video screenState.activeItemId = video.videoHash diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt index efb6f50..6b58036 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/MainActivity.kt @@ -7,7 +7,6 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.content.res.Configuration -import android.net.Uri import android.os.Bundle import cn.learningpad.oraltrainer.sdk.AuthSession import cn.learningpad.oraltrainer.sdk.GestureEvent @@ -17,8 +16,6 @@ import cn.learningpad.oraltrainer.sdk.OralTrainerListener import cn.learningpad.oraltrainer.sdk.OralTrainerSdk import cn.learningpad.oraltrainer.sdk.PlaybackSnapshot import cn.learningpad.oraltrainer.sdk.SentenceBoundary -import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiCallback -import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiResult import cn.learningpad.oraltrainer.sdk.TrainingMediaItem import cn.learningpad.oraltrainer.sdk.TrainingVideoSummary @@ -173,53 +170,6 @@ class MainActivity : Activity() { super.onBackPressed() } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode != PICK_VIDEO_REQUEST || resultCode != RESULT_OK) return - val uri = data?.data ?: return - val persistableFlags = data.flags and Intent.FLAG_GRANT_READ_URI_PERMISSION - if (persistableFlags != 0) { - runCatching { contentResolver.takePersistableUriPermission(uri, persistableFlags) } - } - - val item = TrainingMediaItem( - id = uri.toString(), - title = uri.lastPathSegment ?: "本地视频", - uri = uri, - ) - screenState.activeItemId = item.id - screenState.currentSentenceCount = 0 - controller.loadItem(item) - if (bindings.hasPlayerChrome) { - bindings.lessonTitleText.text = item.title - bindings.sentenceMetaText.text = "句子边界分析中" - bindings.sentenceText.text = "正在匹配服务端句子边界" - bindings.statusText.text = "正在获取本地视频的句子边界..." - } - screenFactory.renderCatalog() - - sdk.sentenceBoundaryApi.fetchForUri(uri, contentResolver, object : SentenceBoundaryApiCallback { - override fun onSuccess(result: SentenceBoundaryApiResult) { - if (screenState.activeItemId != item.id) return - screenState.currentSentenceCount = result.sentences.size - controller.loadItem(item.copy(sentences = result.sentences)) - if (bindings.hasPlayerChrome) { - bindings.statusText.text = "句子边界已加载:${result.sentences.size} 句" - } - } - - override fun onError(error: Throwable) { - if (screenState.activeItemId != item.id) return - if (bindings.hasPlayerChrome) { - bindings.statusText.text = - "句子边界获取失败,使用默认快退/快进:${error.message.orEmpty()}" - bindings.sentenceMetaText.text = "暂无句子边界" - bindings.sentenceText.text = "" - } - } - }) - } - override fun onDestroy() { authCoordinator.reset() testSession.release() @@ -268,19 +218,6 @@ class MainActivity : Activity() { setRootView() } - private fun openLocalVideoPicker() { - @Suppress("DEPRECATION") - startActivityForResult( - Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "video/*" - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - }, - PICK_VIDEO_REQUEST, - ) - } - private fun switchModule(module: Module) { if (module != Module.TEST) { testSession.stopDubbingIfNeeded() @@ -393,10 +330,6 @@ class MainActivity : Activity() { private fun screenActions() = object : SampleScreenActions { override fun onModuleSelected(module: Module) = switchModule(module) - override fun onImportLocalVideo() = openLocalVideoPicker() - - override fun onRefreshCatalog() = catalogController.loadCatalog() - override fun onSeekProgressPreview(progress: Int) { if (!bindings.hasPlayerChrome) return val duration = controller.snapshot().durationMs @@ -474,10 +407,6 @@ class MainActivity : Activity() { override fun onRefreshCourses() = catalogController.refreshCourses() - override fun onTrainCatalogVideoClick(video: TrainingVideoSummary) { - catalogController.useCourseInPlace(video) - } - override fun onLogout() = logout() override fun nickname(): String = diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenActions.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenActions.kt index 4109086..dce1602 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenActions.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenActions.kt @@ -17,13 +17,10 @@ internal interface SentenceExplainActions { internal interface TrainScreenActions : SentenceExplainActions { - fun onImportLocalVideo() - fun onRefreshCatalog() fun onSeekProgressPreview(progress: Int) fun onSeekStart() fun onSeekStop(progress: Int) fun onContinuousPlaybackChanged(enabled: Boolean) - fun onTrainCatalogVideoClick(video: TrainingVideoSummary) } internal interface TestScreenActions : diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenFactory.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenFactory.kt index 5686bb6..7fcccd2 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenFactory.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/SampleScreenFactory.kt @@ -33,11 +33,7 @@ internal class SampleScreenFactory( private val courseScreen = CourseScreen(activity, bindings, screenState, widgets, actions) private val trainScreen = TrainScreen( activity = activity, - bindings = bindings, - screenState = screenState, - widgets = widgets, playerChrome = playerChrome, - actions = actions, ) private val testScreen = TestScreen(activity, bindings, screenState, playerChrome, actions) private val mineScreen = MineScreen(activity, bindings, screenState, widgets, actions) @@ -53,6 +49,10 @@ internal class SampleScreenFactory( return LinearLayout(activity).apply { orientation = LinearLayout.VERTICAL + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT, + ) setBackgroundColor(SampleTheme.background) addView(widgets.moduleTabBar(actions)) when (screenState.activeModule) { @@ -65,24 +65,19 @@ internal class SampleScreenFactory( } fun renderCatalog() { - when (screenState.activeModule) { - Module.COURSE -> courseScreen.renderCatalog() - Module.TRAIN -> trainScreen.renderCatalogStrip() - else -> Unit + if (screenState.activeModule == Module.COURSE) { + courseScreen.renderCatalog() } } fun showCatalogLoading() { - when (screenState.activeModule) { - Module.COURSE -> courseScreen.showLoading() - Module.TRAIN -> trainScreen.showCatalogLoading() - else -> Unit + if (screenState.activeModule == Module.COURSE) { + courseScreen.showLoading() } } fun setCatalogStatus(text: String) { courseScreen.setStatus(text) - trainScreen.setCatalogStatus(text) } fun refreshMyContent(userApi: UserApi, token: String) { diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/PlayerChrome.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/PlayerChrome.kt index c5fa71e..8e6705f 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/PlayerChrome.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/PlayerChrome.kt @@ -4,7 +4,10 @@ import android.app.Activity import android.content.pm.ActivityInfo import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable import android.graphics.Typeface +import android.text.TextUtils import android.view.Gravity import android.view.View import android.view.ViewGroup @@ -14,6 +17,7 @@ import android.widget.FrameLayout import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout +import android.widget.ScrollView import android.widget.SeekBar import android.widget.Switch import android.widget.TextView @@ -40,26 +44,50 @@ internal class PlayerChrome( override val uiContext get() = activity fun createPlayerSection(): View = - FrameLayout(activity).apply { + LinearLayout(activity).apply { + orientation = LinearLayout.VERTICAL background = rounded(SampleTheme.surface, 8f, SampleTheme.border) clipToOutline = true layoutParams = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - 0, - 1f, + ViewGroup.LayoutParams.WRAP_CONTENT, ).withMargins(16.dp, 0, 16.dp, 10.dp) - addView(createPlayerView()) - addView(createPlayerOverlay()) addView( - createRotationButton().apply { - layoutParams = FrameLayout.LayoutParams( - 44.dp, - 44.dp, - Gravity.BOTTOM or Gravity.END, - ).apply { - marginEnd = 12.dp - bottomMargin = 12.dp + createPlayerOverlay().apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) + }, + ) + addView( + object : FrameLayout(activity) { + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = (width * 9) / 16 + super.onMeasure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY), + ) } + }.apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) + addView(createPlayerView()) + addView( + createRotationButton().apply { + layoutParams = FrameLayout.LayoutParams( + 44.dp, + 44.dp, + Gravity.BOTTOM or Gravity.END, + ).apply { + marginEnd = 12.dp + bottomMargin = 12.dp + } + }, + ) }, ) } @@ -70,7 +98,8 @@ internal class PlayerChrome( setPadding(18.dp, 16.dp, 18.dp, 16.dp) layoutParams = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, + 0, + 1f, ).withMargins(16.dp, 0, 16.dp, 12.dp) } @@ -78,7 +107,15 @@ internal class PlayerChrome( FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) addView(createPlayerView()) - addView(createPlayerOverlay()) + addView( + createPlayerOverlay().apply { + layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.TOP, + ) + }, + ) val subtitleContainer = LinearLayout(activity).apply { orientation = LinearLayout.VERTICAL layoutParams = FrameLayout.LayoutParams( @@ -106,6 +143,7 @@ internal class PlayerChrome( textSize = 17f typeface = Typeface.DEFAULT_BOLD maxLines = 1 + ellipsize = TextUtils.TruncateAt.END } bindings.sentenceMetaText = TextView(activity).apply { setTextColor(if (translucent) SampleTheme.accentLight else SampleTheme.accentDeep) @@ -157,28 +195,50 @@ internal class PlayerChrome( background = rounded(SampleTheme.overlayFaint, 0f, null) setPadding(18.dp, 14.dp, 18.dp, 16.dp) } - addView(bindings.lessonTitleText) - if (screenState.activeModule == Module.TRAIN) { - addView(createContinuousPlaybackRow(translucent)) + val topContent = LinearLayout(activity).apply { + orientation = LinearLayout.VERTICAL + addView(bindings.lessonTitleText) + if (screenState.activeModule == Module.TRAIN) { + addView(createContinuousPlaybackRow(translucent)) + } + addView(bindings.sentenceMetaText) + addView(LinearLayout(activity).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.TOP + addView( + bindings.sentenceText, + LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f, + ), + ) + bindings.explainSentenceButton = createExplainButton(translucent) + addView( + bindings.explainSentenceButton, + LinearLayout.LayoutParams(32.dp, 32.dp).apply { marginStart = 10.dp }, + ) + }) } - addView(bindings.sentenceMetaText) - addView(LinearLayout(activity).apply { - orientation = LinearLayout.HORIZONTAL - gravity = Gravity.CENTER_VERTICAL - addView( - bindings.sentenceText, - LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f, - ), - ) - bindings.explainSentenceButton = createExplainButton(translucent) - addView( - bindings.explainSentenceButton, - LinearLayout.LayoutParams(32.dp, 32.dp).apply { marginStart = 10.dp }, - ) - }) + addView( + ScrollView(activity).apply { + isFillViewport = true + isVerticalScrollBarEnabled = false + overScrollMode = View.OVER_SCROLL_NEVER + addView( + topContent, + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ), + ) + }, + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + if (translucent) ViewGroup.LayoutParams.WRAP_CONTENT else 0, + if (translucent) 0f else 1f, + ), + ) addView(bindings.progressBar) addView(bindings.statusText) } @@ -197,12 +257,7 @@ internal class PlayerChrome( val overlay = LinearLayout(activity).apply { orientation = LinearLayout.HORIZONTAL gravity = Gravity.CENTER_VERTICAL - setPadding(12.dp, 12.dp, 12.dp, 12.dp) - layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - Gravity.TOP, - ) + setPadding(12.dp, 10.dp, 12.dp, 8.dp) } bindings.timeText = widgets.overlayPill("--:-- / --:--").apply { layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) @@ -217,11 +272,27 @@ internal class PlayerChrome( ImageButton(activity).apply { setImageResource(R.drawable.ic_screen_rotation) imageTintList = ColorStateList.valueOf(Color.WHITE) - background = rounded(Color.argb(178, 9, 12, 16), 22.dp.toFloat()) + val backgroundSize = 22.dp + val buttonSize = 44.dp + val inset = (buttonSize - backgroundSize) / 2 + background = InsetDrawable( + GradientDrawable().apply { + shape = GradientDrawable.OVAL + setColor(Color.argb(102, 9, 12, 16)) + setSize(backgroundSize, backgroundSize) + }, + inset, + ) scaleType = ImageView.ScaleType.CENTER_INSIDE setPadding(10.dp, 10.dp, 10.dp, 10.dp) contentDescription = "横屏" - layoutParams = LinearLayout.LayoutParams(44.dp, 44.dp) + minimumWidth = 0 + minimumHeight = 0 + clipToOutline = false + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + foreground = null + } + layoutParams = LinearLayout.LayoutParams(buttonSize, buttonSize) setOnClickListener { activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE } diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/SampleWidgets.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/SampleWidgets.kt index bf04c43..578133a 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/SampleWidgets.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/SampleWidgets.kt @@ -153,40 +153,4 @@ internal class SampleWidgets( } } - fun videoCard( - id: String, - title: String, - meta: String, - onClick: () -> Unit, - ): View { - val selected = screenState.activeItemId == id - val titleColor = if (selected) SampleTheme.textDark else Color.WHITE - val metaColor = if (selected) SampleTheme.accentDeep else SampleTheme.textMuted - return LinearLayout(activity).apply { - orientation = LinearLayout.VERTICAL - gravity = Gravity.CENTER_VERTICAL - isClickable = true - background = if (selected) { - rounded(SampleTheme.selected, 8f, SampleTheme.accent) - } else { - rounded(SampleTheme.surface, 8f, SampleTheme.border) - } - setPadding(14.dp, 12.dp, 14.dp, 12.dp) - layoutParams = LinearLayout.LayoutParams(218.dp, 84.dp).withMargins(0, 0, 10.dp, 0) - setOnClickListener { onClick() } - addView(TextView(activity).apply { - text = title - setTextColor(titleColor) - textSize = 15f - typeface = Typeface.DEFAULT_BOLD - maxLines = 2 - }) - addView(TextView(activity).apply { - text = meta - setTextColor(metaColor) - textSize = 12f - setPadding(0, 6.dp, 0, 0) - }) - } - } } diff --git a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/TrainScreen.kt b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/TrainScreen.kt index a611984..9c1bdda 100644 --- a/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/TrainScreen.kt +++ b/android/sample-app/src/main/java/cn/learningpad/oraltrainer/sample/ui/TrainScreen.kt @@ -5,23 +5,14 @@ import android.graphics.Color import android.graphics.Typeface import android.view.Gravity import android.view.View -import android.view.ViewGroup -import android.widget.HorizontalScrollView import android.widget.LinearLayout import android.widget.TextView -import cn.learningpad.oraltrainer.sample.SampleBindings import cn.learningpad.oraltrainer.sample.SampleTheme -import cn.learningpad.oraltrainer.sample.TrainScreenActions -import cn.learningpad.oraltrainer.sample.TrainingScreenState import cn.learningpad.oraltrainer.sample.UiStyled internal class TrainScreen( private val activity: Activity, - private val bindings: SampleBindings, - private val screenState: TrainingScreenState, - private val widgets: SampleWidgets, private val playerChrome: PlayerChrome, - private val actions: TrainScreenActions, ) : UiStyled { override val uiContext get() = activity @@ -29,47 +20,13 @@ internal class TrainScreen( root.addView(createHeader()) root.addView(playerChrome.createPlayerSection()) root.addView(playerChrome.createSentenceSection()) - root.addView(createCatalogSection()) - } - - fun renderCatalogStrip() { - if (!bindings.hasCatalogChrome) return - bindings.catalogList.removeAllViews() - bindings.catalogStatusText.text = screenState.catalogStatus - screenState.catalogVideos.forEach { video -> - val enrolled = screenState.courseEnrollment[video.videoHash] == true - bindings.catalogList.addView( - widgets.videoCard( - id = video.videoHash, - title = video.title, - meta = if (enrolled) { - "已选修 · ${video.sentenceCount} 句" - } else { - "未选修 · 点击选修并进入" - }, - onClick = { actions.onTrainCatalogVideoClick(video) }, - ), - ) - } - } - - fun showCatalogLoading() { - if (!bindings.hasCatalogChrome) return - bindings.catalogList.removeAllViews() - bindings.catalogList.addView(widgets.infoCard("课程加载中...")) - } - - fun setCatalogStatus(text: String) { - if (bindings.hasCatalogChrome) { - bindings.catalogStatusText.text = text - } } private fun createHeader(): View { - val titleBlock = LinearLayout(activity).apply { - orientation = LinearLayout.VERTICAL + return LinearLayout(activity).apply { + orientation = LinearLayout.HORIZONTAL gravity = Gravity.CENTER_VERTICAL - layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) + setPadding(18.dp, 16.dp, 18.dp, 12.dp) addView(TextView(activity).apply { text = "八哥口语" setTextColor(Color.WHITE) @@ -80,52 +37,10 @@ internal class TrainScreen( addView(TextView(activity).apply { text = "外语跟读训练神器" setTextColor(SampleTheme.textMuted) - textSize = 20f - setPadding(1.dp, 4.dp, 0, 0) + textSize = 18f + includeFontPadding = false + setPadding(12.dp, 6.dp, 0, 0) }) } - return LinearLayout(activity).apply { - orientation = LinearLayout.HORIZONTAL - gravity = Gravity.CENTER_VERTICAL - setPadding(18.dp, 16.dp, 18.dp, 12.dp) - addView(titleBlock) - addView(widgets.headerButton("导入") { actions.onImportLocalVideo() }) - addView(widgets.headerButton("刷新") { actions.onRefreshCatalog() }) - } - } - - private fun createCatalogSection(): View { - bindings.catalogStatusText = TextView(activity).apply { - setTextColor(SampleTheme.textMuted) - textSize = 13f - gravity = Gravity.END - text = "正在同步" - layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) - } - bindings.catalogList = LinearLayout(activity).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(0, 10.dp, 0, 4.dp) - } - val header = LinearLayout(activity).apply { - orientation = LinearLayout.HORIZONTAL - gravity = Gravity.CENTER_VERTICAL - addView(TextView(activity).apply { - text = "课程" - setTextColor(Color.WHITE) - textSize = 16f - typeface = Typeface.DEFAULT_BOLD - }) - addView(bindings.catalogStatusText) - } - val scroller = HorizontalScrollView(activity).apply { - isHorizontalScrollBarEnabled = false - addView(bindings.catalogList) - } - return LinearLayout(activity).apply { - orientation = LinearLayout.VERTICAL - setPadding(16.dp, 0, 16.dp, 14.dp) - addView(header) - addView(scroller) - } } }