实现了双屏摄像头显示,添加了部分代码

This commit is contained in:
zxj
2025-07-31 11:40:56 +08:00
parent 56e0a85a4c
commit de4c5f1c75
86 changed files with 3446 additions and 809 deletions
@@ -0,0 +1,123 @@
package com.sw.dualscreen.view
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.GenericItemAdapter
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.BottomSheetDialogBinding
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
import com.sw.dualscreen.model.response.FoodInfo
class CustomBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var binding: BottomSheetDialogBinding
private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
private var checkedItem: FoodInfo? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = BottomSheetDialogBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.ivSearch.setOnClickListener {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.BottomSheetDialogTheme)
setDialogStyle()
}
private fun setDialogStyle() {
dialog?.window?.let {
it.decorView.background = ColorDrawable(Color.TRANSPARENT)
it.decorView.setPadding(0, 0, 0, 0)
it.attributes?.apply {
width = ViewGroup.LayoutParams.MATCH_PARENT
height = ViewGroup.LayoutParams.MATCH_PARENT
}
it.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
companion object {
fun newInstance(): CustomBottomSheetDialog {
return CustomBottomSheetDialog()
}
}
private fun initView() {
adapter = createAdapter()
binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
// 添加间距装饰(12dp)
binding.recyclerview.addItemDecoration(
GridSpacingItemDecoration(
spanCount = 2,
spacing = requireContext().dpToPx(6),
includeEdge = false // 包含边缘间距
)
)
binding.recyclerview.adapter = adapter
binding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
val totalItems = layoutManager.itemCount
// if (!viewModel.isLoading && viewModel.canLoadMore
// && lastVisibleItem >= totalItems - 3
// ) {
// viewModel.getSearchMemberList(
// param = lastText,
// pageNum = viewModel.currentPage
// )
// }
}
})
}
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
return GenericItemAdapter(
items = emptyList(),
bindingInflater = ItemSearchFoodInfoBinding::inflate,
bindCallback = { item, position ->
// this.tvName.text = item.name.maskName()
// this.tvPhone.text = item.phone.maskPhone()
// if (item.id != checkedItem?.id) {
// this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
// } else {
// this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
// }
//
// this.llRoot.setOnClickListener {
// Timber.d("itemClick ${item.name}, position = $position")
// checkedItem = item
// adapter.notifyDataSetChanged()
// }
}
)
}
}
@@ -0,0 +1,129 @@
package com.sw.dualscreen.view;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import com.sw.plate.utils.AppUtil;
public class CustomImageView extends androidx.appcompat.widget.AppCompatImageView {
private AlphaAnimation alphaAnimation;
private String textToDraw;
private Paint mPaint;
private int mTextSize = dpToPixels(26);
private boolean isDrawText = false;
public CustomImageView(Context context) {
super(context);
init();
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
textToDraw = "超标";
mPaint = new Paint();//Paint.ANTI_ALIAS_FLAG
mPaint.setColor(Color.WHITE); // Set your desired color
mPaint.setTextSize(mTextSize); // Set your desired text size
mPaint.setAntiAlias(true);
// Typeface typeface = getResources().getFont(R.font.dakai);
// Typeface typeface = Typeface.create(Typeface.createFromAsset(getContext().getAssets(), "fonts/dakai.TTF"),
// Typeface.BOLD); // 创建Typeface
// mPaint.setTypeface(typeface); // 设置Paint的Typeface
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isDrawText) {
Rect mBounds = new Rect();
mPaint.getTextBounds(textToDraw, 0, textToDraw.length(), mBounds);
mPaint.setShadowLayer(5, 2, 2, Color.GRAY);
float textWidth = mBounds.width();
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
float fontHeight = fontMetrics.bottom - fontMetrics.top;
//设置文字居中
float textBaseY = getHeight() - (getHeight() - fontHeight) / 2 - fontMetrics.bottom;
// textBaseY = 26;
canvas.drawText(textToDraw, (getWidth() - textWidth) / 2, textBaseY, mPaint);
}
}
// 用于设置自定义高度的方法
public void setCustomHeight(int height, boolean isAnimation) {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
int height1 = layoutParams.height;
if (height1 != height) {
if (isAnimation) {
ValueAnimator animator = ValueAnimator.ofInt(height1, height);
animator.addUpdateListener(animation -> {
// 更新视图的高度
int val = (Integer) animation.getAnimatedValue();
ViewGroup.LayoutParams params = getLayoutParams();
params.height = dpToPixels(val);
setLayoutParams(params);
});
animator.setDuration(100); // 动画持续时间500毫秒
animator.start(); // 开始动画
} else {
if (layoutParams == null) {
layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);
} else {
layoutParams.height = dpToPixels(height);
}
setLayoutParams(layoutParams);
}
}
}
private boolean isStartAlphaAnimation = false;
public void startAlphaAnimation() {
if (!isStartAlphaAnimation) {
alphaAnimation = new AlphaAnimation(1.0f, 0.2f); // 从不透明渐变到完全透明
alphaAnimation.setDuration(420); // 设置动画持续时间,单位为毫秒
alphaAnimation.setRepeatCount(AlphaAnimation.INFINITE); // 设置动画重复次数为无限(可选)
startAnimation(alphaAnimation);
isStartAlphaAnimation = true;
}
}
public void cancelAlphaAnimation() {
if (alphaAnimation != null)
alphaAnimation.cancel();
isStartAlphaAnimation = false;
}
public void showText(String textToDraw) {
if (!AppUtil.isEmpty(textToDraw)) {
this.textToDraw = textToDraw;
}
isDrawText = true;
}
public void hideText() {
isDrawText = false;
}
private int dpToPixels(int dp) {
float scale = getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
}
@@ -0,0 +1,163 @@
package com.sw.dualscreen.view
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SpinnerAdapter
import android.widget.TextView
import androidx.appcompat.widget.AppCompatSpinner
import androidx.core.content.ContextCompat
import com.sw.dualscreen.R
class CustomSpinner @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.spinnerStyle
) : AppCompatSpinner(context, attrs, defStyleAttr) {
private var customAdapter: CustomArrayAdapter? = null
private var arrowDrawable: Drawable? = null
private var textColor: Int = 0
private var textSize: Float = 0f
init {
initAttributes(attrs)
setupAdapter()
}
private fun initAttributes(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomSpinner)
try {
textColor = typedArray.getColor(
R.styleable.CustomSpinner_spinnerTextColor,
ContextCompat.getColor(context, android.R.color.black)
)
textSize = typedArray.getDimension(
R.styleable.CustomSpinner_spinnerTextSize,
16f
)
arrowDrawable = typedArray.getDrawable(
R.styleable.CustomSpinner_spinnerArrowDrawable
) ?: ContextCompat.getDrawable(context, R.drawable.ic_arrow_drop_down)
} finally {
typedArray.recycle()
}
}
private fun setupAdapter() {
customAdapter = CustomArrayAdapter(context, createSpinnerView())
customAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
super.setAdapter(customAdapter)
}
private fun createSpinnerView(): View {
return LinearLayout(context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
// 文本视图
addView(TextView(context).apply {
layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
id = android.R.id.text1
setTextColor(textColor)
textSize = this@CustomSpinner.textSize
setPadding(16.dpToPx(), 12.dpToPx(), 8.dpToPx(), 12.dpToPx())
})
// 箭头图标
addView(ImageView(context).apply {
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
marginEnd = 8.dpToPx()
}
setImageDrawable(arrowDrawable)
contentDescription = "下拉箭头"
})
}
}
override fun setAdapter(adapter: SpinnerAdapter?) {
if (adapter is CustomArrayAdapter) {
super.setAdapter(adapter)
customAdapter = adapter
} else {
customAdapter?.clear()
adapter?.let {
for (i in 0 until it.count) {
customAdapter?.add(it.getItem(i)?.toString() ?: "")
}
}
}
}
fun setItems(items: List<String>) {
customAdapter?.run {
clear()
addAll(items.toMutableList()) // 确保传入可变集合
notifyDataSetChanged()
}
}
fun setItems(items: Array<String>) {
setItems(items.toList())
}
fun setArrowDrawable(drawable: Drawable) {
arrowDrawable = drawable
setupAdapter() // 重新初始化适配器以应用新图标
}
fun setArrowDrawable(resId: Int) {
ContextCompat.getDrawable(context, resId)?.let {
setArrowDrawable(it)
}
}
override fun getSelectedItem(): String? {
return if (selectedItemPosition in 0 until (customAdapter?.count ?: 0)) {
customAdapter?.getItem(selectedItemPosition)
} else {
null
}
}
private inner class CustomArrayAdapter(
context: Context,
private val spinnerView: View
) : ArrayAdapter<String>(context, 0) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: spinnerView
val textView = view.findViewById<TextView>(android.R.id.text1)
textView.text = getItem(position)
return view
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = super.getDropDownView(position, convertView, parent) as TextView
view.setTextColor(textColor)
view.textSize = textSize
view.setPadding(16.dpToPx(), 12.dpToPx(), 16.dpToPx(), 12.dpToPx())
return view
}
}
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).toInt()
}
@@ -0,0 +1,56 @@
package com.sw.dualscreen.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
class CylinderDrawView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 圆柱属性(默认红色)
var cylinderColor: Int = Color.RED
set(value) {
field = value
invalidate()
}
var cylinderWidth: Int = 120 // 默认宽度(px
set(value) {
field = value
requestLayout()
}
var cylinderHeight: Int = 360 // 默认高度(px
set(value) {
field = value
requestLayout()
}
private val paint = Paint().apply {
isAntiAlias = true // 抗锯齿
style = Paint.Style.FILL // 填充模式
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val measuredWidth = resolveSize(cylinderWidth, widthMeasureSpec)
val measuredHeight = resolveSize(cylinderHeight, heightMeasureSpec)
setMeasuredDimension(measuredWidth, measuredHeight)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = cylinderColor
// 绘制圆角矩形(半径=宽度的一半,形成圆柱效果)
val radius = cylinderWidth / 2f
canvas.drawRoundRect(
0f, 0f,
cylinderWidth.toFloat(), cylinderHeight.toFloat(),
radius, radius,
paint
)
}
}
@@ -0,0 +1,104 @@
package com.sw.dualscreen.view
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import com.sw.dualscreen.R
class CylinderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val valueText: TextView
private val unitText: TextView
private val cylinderDrawView: CylinderDrawView
private val topImage: ImageView
private val bottomImage: ImageView
private val bottomText: TextView
init {
// 加载内部布局
LayoutInflater.from(context).inflate(R.layout.view_cylinder, this, true)
// 绑定控件
valueText = findViewById(R.id.valueText)
unitText = findViewById(R.id.unitText)
cylinderDrawView = findViewById(R.id.cylinderDrawView)
topImage = findViewById(R.id.topImage)
bottomImage = findViewById(R.id.bottomImage)
bottomText = findViewById(R.id.bottomText)
// 解析自定义属性
context.obtainStyledAttributes(attrs, R.styleable.CylinderView).apply {
// 顶部文字:数值+单位合并
val value = getString(R.styleable.CylinderView_valueText) ?: ""
val unit = getString(R.styleable.CylinderView_unitText) ?: ""
valueText.text = value
valueText.textSize = getDimension(R.styleable.CylinderView_valueTextSize, 20f)
valueText.setTextColor(
getColor(
R.styleable.CylinderView_valueTextColor,
0xFFFFFFFF.toInt()
)
)
unitText.text = unit
unitText.textSize = getDimension(R.styleable.CylinderView_valueTextSize, 10f)
unitText.setTextColor(
getColor(
R.styleable.CylinderView_valueTextColor,
0xFFFFFFFF.toInt()
)
)
// 圆柱属性
cylinderDrawView.cylinderColor =
getColor(R.styleable.CylinderView_cylinderColor, Color.RED)
cylinderDrawView.cylinderWidth =
getDimensionPixelSize(R.styleable.CylinderView_cylinderWidth, 60)
cylinderDrawView.cylinderHeight =
getDimensionPixelSize(R.styleable.CylinderView_cylinderHeight, 180)
getResourceId(R.styleable.CylinderView_topImageSrc, -1).takeIf { it != -1 }?.let {
topImage.setImageResource(it)
// val layoutParams = cylinderDrawView.layoutParams
// layoutParams.height = (layoutParams.width / 2.45).toInt()
// topImage.layoutParams = layoutParams
}
// 底部图片
getResourceId(R.styleable.CylinderView_bottomImageSrc, -1).takeIf { it != -1 }?.let {
bottomImage.setImageResource(it)
// val layoutParams = cylinderDrawView.layoutParams
// layoutParams.height = 44
// bottomImage.layoutParams = layoutParams
}
// 底部文字
bottomText.text = getString(R.styleable.CylinderView_bottomText) ?: "热量"
bottomText.textSize = getDimension(R.styleable.CylinderView_bottomTextSize, 16f)
bottomText.setTextColor(
getColor(
R.styleable.CylinderView_bottomTextColor,
0xFFFFFFFF.toInt()
)
)
recycle() // 回收属性
}
}
fun setToImageSize() {
// topImage.layoutParams = LayoutParams.
}
fun setBottomSize() {
}
}