From b358bdb8896ddfaa1ba40f75cd2e5eccd050669f Mon Sep 17 00:00:00 2001 From: wangyu Date: Thu, 29 Jan 2026 17:25:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=BC=95=E5=AF=BC=E9=A1=B5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=88=91=E7=9A=84?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=BC=81=E4=B8=9A=E6=9C=AA=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E7=8A=B6=E6=80=81,=202.4.0/240?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- .../blzb/ui/activity/LauncherActivity.kt | 3 +- .../blzb/ui/fragment/guide/GuideFragment.kt | 5 + .../blzb/ui/fragment/guide/GuideViewModel.kt | 14 ++ .../guide/adapter/GuideItem2Area2Adapter.kt | 19 +-- .../fragment/guide/item/GuideItem2Fragment.kt | 56 +++++-- .../fragment/guide/item/GuideItem3Fragment.kt | 7 +- .../fragment/guide/item/GuideItem5Fragment.kt | 2 +- .../ui/fragment/guide/vip/GuideVipFragment.kt | 154 +++++++++++++----- .../blzb/ui/fragment/mine/MineFragment.kt | 5 +- .../com/cheng/blzb/widget/BarChartView.kt | 129 +++++++++------ .../blzb/widget/LinearTopSmoothScroller.kt | 56 +++++++ .../main/res/drawable/shape_00d9ea_dp6.xml | 7 + .../main/res/drawable/shape_ff9e3a_dp6.xml | 7 + .../main/res/layout/fragment_guide_vip.xml | 46 +++++- app/src/main/res/layout/fragment_mine.xml | 3 +- app/src/main/res/layout/layout_x_axis_bar.xml | 38 +++++ .../main/res/layout/listitem_bar_chart_x.xml | 46 ++---- .../listitem_guide4_selected_keyword.xml | 6 +- .../main/res/layout/listitem_guide_item4.xml | 6 +- 20 files changed, 436 insertions(+), 177 deletions(-) create mode 100644 app/src/main/java/com/cheng/blzb/widget/LinearTopSmoothScroller.kt create mode 100644 app/src/main/res/drawable/shape_00d9ea_dp6.xml create mode 100644 app/src/main/res/drawable/shape_ff9e3a_dp6.xml create mode 100644 app/src/main/res/layout/layout_x_axis_bar.xml diff --git a/app/build.gradle b/app/build.gradle index 05db4e7..5eb4477 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { applicationId "com.cheng.BoLe" minSdk 26 targetSdk 34 - versionCode 200 - versionName "2.0.0" + versionCode 240 + versionName "2.4.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/cheng/blzb/ui/activity/LauncherActivity.kt b/app/src/main/java/com/cheng/blzb/ui/activity/LauncherActivity.kt index f57cdca..eb19a41 100644 --- a/app/src/main/java/com/cheng/blzb/ui/activity/LauncherActivity.kt +++ b/app/src/main/java/com/cheng/blzb/ui/activity/LauncherActivity.kt @@ -116,8 +116,7 @@ class LauncherActivity : BaseActivity() { LoginActivity.start(this@LauncherActivity) } } else { -// startActivity() - startActivity() + startActivity() } finish() } diff --git a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideFragment.kt b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideFragment.kt index 7693a76..47229fb 100644 --- a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideFragment.kt +++ b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideFragment.kt @@ -60,6 +60,7 @@ class GuideFragment : BaseFragment( super.initData() mViewModel.getHotWordList() mViewModel.getCityList() + mViewModel.getBidTypeList() EventReportManager.eventReport(EventConstants.GUIDE_LAUNCH, "", "") } @@ -93,6 +94,10 @@ class GuideFragment : BaseFragment( UserConfigManager.saveAreaList(it) } + mViewModel.bidTypeLiveData.observe(this) { + UserConfigManager.saveBidTypes(it) + } + val guideEvent = RxBus.defaultInstance.toObservable(GuideEvent::class.java).subscribe { if (it.toIndex < fragmentList.size) { binding.viewPager.currentItem = it.toIndex diff --git a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideViewModel.kt b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideViewModel.kt index e584132..0befc22 100644 --- a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideViewModel.kt +++ b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/GuideViewModel.kt @@ -2,6 +2,7 @@ package com.cheng.blzb.ui.fragment.guide import androidx.lifecycle.MutableLiveData import com.cheng.blzb.bean.AreaEntity +import com.cheng.blzb.bean.BidTypeEntity import com.cheng.blzb.bean.GuideTotalBidEntity import com.cheng.blzb.bean.HotWordEntity import com.cheng.blzb.net.ApiFactory @@ -12,6 +13,7 @@ import com.example.base.viewmodel.BaseViewModel class GuideViewModel :BaseViewModel(){ val hotWordLiveData = MutableLiveData>() val cityLiveData = MutableLiveData>() + val bidTypeLiveData = MutableLiveData>() val userCityLiveData = MutableLiveData() val totalLiveData = MutableLiveData() @@ -39,6 +41,18 @@ class GuideViewModel :BaseViewModel(){ }) } + fun getBidTypeList() { + launchOnUiTryCatch({ + val response = ApiFactory.apiService.getBidTypeList() + if (response.status) { + bidTypeLiveData.postValue(response.data) + } else toast(response.message, true) + }, { + setError(it) + L.d(it) + }) + } + fun getUserCity() { launchOnUiTryCatch({ val response = ApiFactory.apiService.getUserCity() diff --git a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/adapter/GuideItem2Area2Adapter.kt b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/adapter/GuideItem2Area2Adapter.kt index 14245cc..62a5523 100644 --- a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/adapter/GuideItem2Area2Adapter.kt +++ b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/adapter/GuideItem2Area2Adapter.kt @@ -11,7 +11,9 @@ import com.example.base.decoration.GridSpaceItemDecoration import com.example.base.extensions.onClick import com.example.base.utils.DensityUtils -class GuideItem2Area2Adapter(val area1Adapter: GuideItem2Area1Adapter): BaseQuickAdapter(R.layout.listitem_guide_area2) { +class GuideItem2Area2Adapter(): BaseQuickAdapter(R.layout.listitem_guide_area2) { + private var mListener: (() -> Unit)? = null + @SuppressLint("NotifyDataSetChanged") override fun convert(holder: BaseViewHolder, item: AreaEntity) { val tvName = holder.getView(R.id.tv_name) @@ -38,6 +40,7 @@ class GuideItem2Area2Adapter(val area1Adapter: GuideItem2Area1Adapter): BaseQuic val cityItem = cityAdapter.getItem(position) cityItem.isChecked = !cityItem.isChecked notifyDataSetChanged() + mListener?.invoke() } tvCheckAll.onClick { @@ -59,16 +62,8 @@ class GuideItem2Area2Adapter(val area1Adapter: GuideItem2Area1Adapter): BaseQuic } } notifyDataSetChanged() + mListener?.invoke() } - - tvName.onClick { - data.find { it.isChecked }?.isChecked = false - item.isChecked = true - notifyDataSetChanged() - area1Adapter.notifyDataSetChanged() - } - - holder.setGone(R.id.rv_city, !item.isChecked) } private fun updateCheckState(textView: TextView, isCheckAll: Boolean) { @@ -78,4 +73,8 @@ class GuideItem2Area2Adapter(val area1Adapter: GuideItem2Area1Adapter): BaseQuic textView.setCompoundDrawablesWithIntrinsicBounds(R.mipmap.ic_check_false, 0, 0, 0) } } + + fun setOnCityClickListener(listener: () -> Unit) { + this.mListener = listener + } } \ No newline at end of file diff --git a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/item/GuideItem2Fragment.kt b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/item/GuideItem2Fragment.kt index 837456e..bb47b8a 100644 --- a/app/src/main/java/com/cheng/blzb/ui/fragment/guide/item/GuideItem2Fragment.kt +++ b/app/src/main/java/com/cheng/blzb/ui/fragment/guide/item/GuideItem2Fragment.kt @@ -4,6 +4,8 @@ import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.annotation.SuppressLint import androidx.core.animation.addListener +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.cheng.blzb.bean.AreaEntity import com.cheng.blzb.common.Constants import com.cheng.blzb.databinding.FragmentGuideItem2Binding @@ -13,6 +15,7 @@ import com.cheng.blzb.ui.fragment.guide.GuideFragment import com.cheng.blzb.ui.fragment.guide.GuideViewModel import com.cheng.blzb.ui.fragment.guide.adapter.GuideItem2Area1Adapter import com.cheng.blzb.ui.fragment.guide.adapter.GuideItem2Area2Adapter +import com.cheng.blzb.widget.LinearTopSmoothScroller import com.example.base.common.RxBus import com.example.base.extensions.onClick import com.example.base.extensions.toast @@ -24,7 +27,7 @@ import com.example.base.utils.ScreenUtils class GuideItem2Fragment: BaseFragment() { private val area1Adapter by lazy { GuideItem2Area1Adapter() } - private val area2Adapter by lazy { GuideItem2Area2Adapter(area1Adapter) } + private val area2Adapter by lazy { GuideItem2Area2Adapter() } private var canClick = false @@ -36,20 +39,13 @@ class GuideItem2Fragment: BaseFragment - area.children.forEach { it.isChecked = true } - } - area1Adapter.setList(areaList) - area2Adapter.setList(areaList) - } - override fun onLazyLoad() { super.onLazyLoad() + val areaList = UserConfigManager.getAreaList() + areaList.add(0, AreaEntity(0, name = "全国地区", isChecked = true)) + area1Adapter.setList(areaList) + area2Adapter.setList(areaList) + startAnim() } @@ -60,10 +56,33 @@ class GuideItem2Fragment: BaseFragment() + area2Adapter.data.forEach { area -> + selectList.addAll(area.children.filter { it.isChecked }) + } + binding.tvCityCount.text = "${selectList.size}" + } + + binding.rvArea2.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + val layoutManager = binding.rvArea2.layoutManager as LinearLayoutManager + val firstPosition = layoutManager.findFirstCompletelyVisibleItemPosition() + + if (firstPosition >= 0) { + val firstItem = area1Adapter.getItem(firstPosition) + area1Adapter.data.find { it.isChecked }?.isChecked = false + firstItem.isChecked = true + area1Adapter.notifyDataSetChanged() + } + } + }) + binding.btnNext.onClick { if (canClick) { val selectList = mutableListOf() @@ -80,6 +99,13 @@ class GuideItem2Fragment: BaseFragment when(index) { @@ -80,10 +80,7 @@ class GuideItem3Fragment : BaseFragment() { private val userTipsAdapter by lazy { GuideVipUserAdapter(requireContext(), userTipsList) } @@ -81,6 +82,7 @@ class GuideVipFragment: BaseFragment private lateinit var api: IWXAPI private var isAgree = false private var totalPrice = 0f + private var lastGoodsPrice = 0f private val goodsAdapter by lazy { GuideVipAdapter() } @@ -93,11 +95,14 @@ class GuideVipFragment: BaseFragment override fun initView() { super.initView() + setStatusBarLight(false) + binding.tvTotalInfoCount1.typeface = Constants.dDIN_PRO_M binding.tvTotalInfoCount2.typeface = Constants.dDIN_PRO_M binding.tvTotalInfoCount3.typeface = Constants.dDIN_PRO_M binding.tvTotalInfoCount4.typeface = Constants.dDIN_PRO_M binding.tvPrice.typeface = Constants.dDIN_PRO_M + binding.tvPay.typeface = Constants.almmsht binding.rvUser.adapter = userTipsAdapter @@ -109,6 +114,8 @@ class GuideVipFragment: BaseFragment binding.cbAgree.visibility = if (UserConfigManager.isPayAgreementEnable()) View.VISIBLE else View.GONE binding.tvAgree.visibility = if (UserConfigManager.isPayAgreementEnable()) View.VISIBLE else View.GONE + binding.tvChartTitle.text = "月份:${Calendar.getInstance().get(Calendar.MONTH) + 1}月 单位:条" + binding.layoutContent.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { override fun onGlobalLayout() { mViewModel.getGoodsList() @@ -153,27 +160,28 @@ class GuideVipFragment: BaseFragment binding.layoutTotalInfo.visible() binding.layoutChart.gone() updateTabStyle(binding.tvTab1) + setTotalData() } binding.tvTab2.onClick { binding.layoutTotalInfo.gone() binding.layoutChart.visible() updateTabStyle(binding.tvTab2) - setChartData() + setDailyChartData() } binding.tvTab3.onClick { binding.layoutTotalInfo.gone() binding.layoutChart.visible() updateTabStyle(binding.tvTab3) - setChartData() + setBidTypeChartData() } binding.tvTab4.onClick { binding.layoutTotalInfo.gone() binding.layoutChart.visible() updateTabStyle(binding.tvTab4) - setChartData() + setCityChartData() } binding.tvAliPay.onClick { @@ -276,7 +284,6 @@ class GuideVipFragment: BaseFragment mViewModel.updateLiveData.observe(this) { updateInfoList.clear() updateInfoList.addAll(it) - binding.tvChartTitle.text = "月份:${Calendar.getInstance().get(Calendar.MONTH) + 1}月 单位:条" } val payStatusDisposable = RxBus.defaultInstance.toObservable(PayStatusEvent::class.java).subscribe { @@ -312,27 +319,6 @@ class GuideVipFragment: BaseFragment arguments?.getSerializable("total") as? GuideTotalBidEntity } - SpanUtils.with(binding.tvTotalInfoCount1) - .append("${totalInfo?.bidCount}") - .append("条") - .setFontSize(10, true) - .create() - SpanUtils.with(binding.tvTotalInfoCount2) - .append("${totalInfo?.hasContact}") - .append("条") - .setFontSize(10, true) - .create() - SpanUtils.with(binding.tvTotalInfoCount3) - .append("${totalInfo?.maxMoney}") - .append("亿元") - .setFontSize(10, true) - .create() - SpanUtils.with(binding.tvTotalInfoCount4) - .append("${totalInfo?.yesterdayCount}") - .append("条") - .setFontSize(10, true) - .create() - val hotWordListStr = arguments?.getString("hotWords") if (!TextUtils.isEmpty(hotWordListStr)) { hotWordChildList.addAll(Gson().fromJson>(hotWordListStr, object : TypeToken>() {}.type)) @@ -346,13 +332,78 @@ class GuideVipFragment: BaseFragment } } - private fun setChartData() { - val dateList = getDateList(updateInfoList, System.currentTimeMillis()) - val valueList = mutableListOf() - updateInfoList.forEachIndexed { index, item -> - valueList.add(BarChartView.XAxisValue(item.count.toFloat(), dateList[index])) + private fun setTotalData() { + totalInfo?.let { + val valueAnim = ValueAnimator.ofFloat(0f, 1f) + valueAnim.duration = 1000 + valueAnim.addUpdateListener { animator -> + val percent = animator.animatedValue as Float + SpanUtils.with(binding.tvTotalInfoCount1) + .append("${(it.bidCount * percent).toInt()}") + .append("条") + .setFontSize(10, true) + .create() + SpanUtils.with(binding.tvTotalInfoCount2) + .append("${(it.hasContact * percent).toInt()}") + .append("条") + .setFontSize(10, true) + .create() + SpanUtils.with(binding.tvTotalInfoCount3) + .append("${(it.maxMoney * percent).toInt()}") + .append("亿元") + .setFontSize(10, true) + .create() + SpanUtils.with(binding.tvTotalInfoCount4) + .append("${(it.yesterdayCount * percent).toInt()}") + .append("条") + .setFontSize(10, true) + .create() + } + valueAnim.start() } - binding.barChart.setData(valueList) + } + + private fun setDailyChartData() { + val dateList = getDateList(updateInfoList, System.currentTimeMillis()) + val dataList = mutableListOf() + updateInfoList.forEachIndexed { index, item -> + dataList.add(BarChartView.BarDataSet(dateList[index], listOf(item.count.toFloat()))) + } + binding.barChart.setData(dataList) + binding.tvLegend1.gone() + binding.tvLegend2.gone() + } + + private fun setBidTypeChartData() { + totalInfo?.let { + val percentArray = intArrayOf(4, 6, 34, 5, 28, 7, 45, 1) + val bidTypeArray = arrayOf("竞争性磋商", "更正公告", "招标公告", "招标计划", "采购公告", "合同公告", "中标公告", "终止公告") + val dataList = mutableListOf() + bidTypeArray.forEachIndexed { index, item -> + dataList.add(BarChartView.BarDataSet(item, listOf((it.bidCount * percentArray[index] / 100).toFloat()))) + } + binding.barChart.setData(dataList) + binding.tvLegend1.gone() + binding.tvLegend2.gone() + } + } + + private fun setCityChartData() { + val array1 = intArrayOf( + 134089, 93969, 69947, 186730, 103113, 60920, 32955, 156973, 43129, 232609, 84234, 72890, 44618, 48437, 43981, + 102764, 139170, 64218, 45001, 71689, 39366, 113385, 51259, 33253, 31930, 16348, 19388, 17568, 39384, 21928, 5853 + ) + val array2 = intArrayOf( + 14467, 9491, 7524, 6651, 6036, 5505, 4962, 4584, 4397, 3982, 3609, 3472, 3295, 3157, 3103, + 3078, 1907, 1803, 1690, 1555, 1421, 1128, 892, 758, 731, 708, 525, 525, 436, 328, 254 + ) + val dataList = mutableListOf() + UserConfigManager.getCityList().forEachIndexed { index, item -> + dataList.add(BarChartView.BarDataSet(item.name, listOf(array1[index].toFloat(), array2[index].toFloat()))) + } + binding.barChart.setData(dataList) + binding.tvLegend1.visible() + binding.tvLegend2.visible() } private fun startAnim() { @@ -395,6 +446,7 @@ class GuideVipFragment: BaseFragment infoRotateAnim.addListener(onStart = { binding.layoutInfo.visible() }, onEnd = { + setTotalData() infoLine1TransAnim.start() infoLine2TransAnim.start() }) @@ -507,18 +559,24 @@ class GuideVipFragment: BaseFragment val globalPayType = UserConfigManager.getPayType() if (list?.find { it == "weixin" } != null && globalPayType.find { it == "weixin" } != null) { binding.tvWxPay.visible() + binding.viewLine1.visible() } else { binding.tvWxPay.gone() + binding.viewLine1.gone() } if (list?.find { it == "alipay" } != null && globalPayType.find { it == "alipay" } != null) { binding.tvAliPay.visible() + binding.viewLine2.visible() } else { binding.tvAliPay.gone() + binding.viewLine2.gone() } if (list?.find { it == "bank" } != null && globalPayType.find { it == "bank" } != null) { binding.tvBankPay.visible() + binding.viewLine3.visible() } else { binding.tvBankPay.gone() + binding.viewLine3.gone() } if (goodsEntity?.pay_type!!.startsWith("weixin") && binding.tvWxPay.isVisible) { @@ -553,14 +611,24 @@ class GuideVipFragment: BaseFragment @SuppressLint("SetTextI18n") private fun setPrice(price: Float) { + lastGoodsPrice = totalPrice totalPrice = if (price < 0) 0f else price - SpanUtils.with(binding.tvPrice) - .append("¥") - .setFontSize(12, true) - .append(DecimalFormat("0.##").format(totalPrice)) - .append(formatPricePeriod(goodsEntity!!.value)) - .setFontSize(12, true) - .create() + + val valueAnim = ValueAnimator.ofFloat(0f, 1f).setDuration(500) + valueAnim.addUpdateListener { + val percent = it.animatedValue as Float + val diffPrice = totalPrice - lastGoodsPrice + val animPrice = if (percent == 1f) totalPrice * percent else (lastGoodsPrice + diffPrice * percent).toInt() + SpanUtils.with(binding.tvPrice) + .append("¥") + .setFontSize(12, true) + .append(DecimalFormat("0.##").format(animPrice)) + .append(formatPricePeriod(goodsEntity!!.value)) + .setFontSize(12, true) + .create() + } + valueAnim.start() + binding.tvSavedPrice.text = "已优惠${DecimalFormat("0.##").format(goodsEntity!!.origin_price.toFloat() - totalPrice)}" } @@ -652,9 +720,13 @@ class GuideVipFragment: BaseFragment @SuppressLint("UnsafeOptInUsageError") private fun setBackPressed() { if (BuildCompat.isAtLeastT()) { - requireActivity().onBackInvokedDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT) {} + requireActivity().onBackInvokedDispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT) { + requireActivity().startActivity() + } } else { - requireActivity().onBackPressedDispatcher.addCallback(this) {} + requireActivity().onBackPressedDispatcher.addCallback(this) { + requireActivity().startActivity() + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/cheng/blzb/ui/fragment/mine/MineFragment.kt b/app/src/main/java/com/cheng/blzb/ui/fragment/mine/MineFragment.kt index 8114db8..62e2ebe 100644 --- a/app/src/main/java/com/cheng/blzb/ui/fragment/mine/MineFragment.kt +++ b/app/src/main/java/com/cheng/blzb/ui/fragment/mine/MineFragment.kt @@ -181,7 +181,10 @@ class MineFragment : BaseFragment() { else -> binding.tvAuthStatus.gone() } } else { - binding.tvAuthStatus.gone() + binding.tvAuthStatus.text = "未认证" + binding.tvAuthStatus.setTextColor(getColor(R.color.color_ff592b)) + binding.tvAuthStatus.setCompoundDrawablesWithIntrinsicBounds(R.drawable.shape_auth_fail_dot, 0, 0, 0) + binding.tvAuthStatus.visible() } } diff --git a/app/src/main/java/com/cheng/blzb/widget/BarChartView.kt b/app/src/main/java/com/cheng/blzb/widget/BarChartView.kt index b93c2c9..ede6479 100644 --- a/app/src/main/java/com/cheng/blzb/widget/BarChartView.kt +++ b/app/src/main/java/com/cheng/blzb/widget/BarChartView.kt @@ -5,9 +5,10 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.animation.addListener -import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.chad.library.adapter.base.BaseQuickAdapter @@ -23,54 +24,56 @@ class BarChartView @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { + private val xAxisAdapter by lazy { XAxisAdapter() } + + private val yAxisAdapter by lazy { YAxisAdapter() } + private val xAxisColumnCount = 6 + private val yAxisRowCount = 6 private var maxYAxisValue = 0f - /** - * 设置X轴数据 - */ - private fun setXAxisData(data: List, type: Int = 1) { - val rvXAxis = RecyclerView(context) - val adapter = XAxisAdapter(type) - rvXAxis.adapter = adapter - rvXAxis.layoutManager = GridLayoutManager(context, 7) - val xAxisLp = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - xAxisLp.marginStart = DensityUtils.dp2px(50f) - xAxisLp.marginEnd = DensityUtils.dp2px(20f) - rvXAxis.layoutParams = xAxisLp - addView(rvXAxis) - - adapter.setList(data) + init { + initXAxisData() + initYAxisData() } /** - * 设置Y轴数据 + * 初始化X轴 */ - private fun setYAxisData(data: List) { + private fun initXAxisData() { + val rvXAxis = RecyclerView(context) + rvXAxis.adapter = xAxisAdapter + rvXAxis.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) + val xAxisLp = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + xAxisLp.marginStart = DensityUtils.dp2px(40f) + rvXAxis.layoutParams = xAxisLp + addView(rvXAxis) + } + + /** + * 初始化Y轴 + */ + private fun initYAxisData() { val rvYAxis = RecyclerView(context) - val adapter = YAxisAdapter() - rvYAxis.adapter = adapter - rvYAxis.layoutManager = LinearLayoutManager(context) + rvYAxis.adapter = yAxisAdapter + rvYAxis.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) val yAxisLp = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) yAxisLp.bottomMargin = DensityUtils.dp2px(15f) rvYAxis.layoutParams = yAxisLp - addView(rvYAxis) - - adapter.setList(data) + rvYAxis.overScrollMode = OVER_SCROLL_NEVER + addView(rvYAxis, 0) } /** * 设置数据 */ - fun setData(vararg xValueLists: List) { - removeAllViews() - - val allValueList = mutableListOf() - xValueLists.forEach { allValueList.addAll(it) } + fun setData(data: List) { + val allValues = mutableListOf() + data.forEach { allValues.addAll(it.values) } val yValueList = mutableListOf() - val rowValue = allValueList.maxBy { it.value }.value / yAxisRowCount + val rowValue = allValues.maxBy { it } / yAxisRowCount val value = if (rowValue > 10) { var bigNum = rowValue.toInt().toFloat() val numLength = rowValue.toInt().toString().length @@ -89,50 +92,68 @@ class BarChartView @JvmOverloads constructor( } maxYAxisValue = value * yAxisRowCount - setYAxisData(yValueList) - xValueLists.forEachIndexed { index, xAxisValues -> - setXAxisData(xAxisValues, index + 1) - } + xAxisAdapter.setList(data.map { XAxisValue(it.key, it.values) }) + yAxisAdapter.setList(yValueList) } - data class XAxisValue(val value: Float, val date: String, var anim: Boolean = true) + data class BarDataSet(val key: String, val values: List) + + data class XAxisValue(val key: String, val values: List, var anim: Boolean = true) data class YAxisValue(val value: Float) - inner class XAxisAdapter(val type: Int) : BaseQuickAdapter(R.layout.listitem_bar_chart_x) { + inner class XAxisAdapter() : BaseQuickAdapter(R.layout.listitem_bar_chart_x) { override fun convert(holder: BaseViewHolder, item: XAxisValue) { - holder.setText(R.id.tv_value, DecimalFormat("0.#").format(item.value)) - holder.setText(R.id.tv_date, item.date) - if (type == 1) { - holder.setImageResource(R.id.iv_bar, R.mipmap.ic_bar_chart1) - holder.setImageResource(R.id.iv_bar_top, R.mipmap.ic_bar_chart_top1) - } else { - holder.setImageResource(R.id.iv_bar, R.mipmap.ic_bar_chart2) - holder.setImageResource(R.id.iv_bar_top, R.mipmap.ic_bar_chart_top2) - } + val lp = holder.itemView.layoutParams as RecyclerView.LayoutParams + lp.width = recyclerView.measuredWidth / xAxisColumnCount + holder.itemView.layoutParams = lp - if (item.anim) { - startAnimation(holder.getView(R.id.iv_bar), item) - item.anim = false - } else { - holder.setVisible(R.id.iv_bar, true) + holder.setText(R.id.tv_key, item.key) + + val container = holder.getView(R.id.layout_container) + container.removeAllViews() + + item.values.forEachIndexed { index, value -> + val view = inflate(context, R.layout.layout_x_axis_bar, null) + val tvValue = view.findViewById(R.id.tv_value) + val ivBar = view.findViewById(R.id.iv_bar) + val ivBarTop = view.findViewById(R.id.iv_bar_top) + + tvValue.text = DecimalFormat("0.#").format(value) + if (index == 0) { + ivBar.setImageResource(R.mipmap.ic_bar_chart1) + ivBarTop.setImageResource(R.mipmap.ic_bar_chart_top1) + } else { + ivBar.setImageResource(R.mipmap.ic_bar_chart2) + ivBarTop.setImageResource(R.mipmap.ic_bar_chart_top2) + } + container.addView(view) + + val barHeight = (value / maxYAxisValue * recyclerView.measuredHeight * yAxisRowCount / (yAxisRowCount + 1)).toInt() - DensityUtils.dp2px(10f) + if (item.anim) { + startAnimation(ivBar, barHeight) + item.anim = false + } else { + val lp = ivBar.layoutParams as ConstraintLayout.LayoutParams + lp.height = barHeight.coerceAtLeast(DensityUtils.dp2px(7f)) + ivBar.layoutParams = lp + ivBar.visible() + } } } - private fun startAnimation(barView: View, item: XAxisValue) { - val itemHeight = (item.value / maxYAxisValue * recyclerView.measuredHeight * yAxisRowCount / (yAxisRowCount + 1)).toInt() - DensityUtils.dp2px(10f) + private fun startAnimation(barView: View, height: Int) { val valueAnim = ValueAnimator.ofFloat(0f, 1f) valueAnim.addUpdateListener { animator -> val lp = barView.layoutParams as ConstraintLayout.LayoutParams val value = animator.animatedValue as Float - lp.height = (value * itemHeight).coerceAtLeast(DensityUtils.dp2px(6f).toFloat()).toInt() + lp.height = (value * height).coerceAtLeast(DensityUtils.dp2px(6f).toFloat()).toInt() barView.layoutParams = lp } valueAnim.addListener(onStart = { barView.visible() }) valueAnim.duration = 2000 - valueAnim.startDelay = 1000 valueAnim.start() } } diff --git a/app/src/main/java/com/cheng/blzb/widget/LinearTopSmoothScroller.kt b/app/src/main/java/com/cheng/blzb/widget/LinearTopSmoothScroller.kt new file mode 100644 index 0000000..f7c245c --- /dev/null +++ b/app/src/main/java/com/cheng/blzb/widget/LinearTopSmoothScroller.kt @@ -0,0 +1,56 @@ +package com.cheng.blzb.widget + +import android.content.Context +import android.util.DisplayMetrics +import androidx.recyclerview.widget.LinearSmoothScroller + + +class LinearTopSmoothScroller(val context: Context, needFast: Boolean) : LinearSmoothScroller(context) { + /** + * speed 值越大滚动越慢 + */ + private var speed = 0.03f + + /** + * @param context context + * @param needFast 是否需要快速滑动 + */ + init { + if (needFast) { + setScrollFast() + } else { + setScrollSlowly() + } + } + + override fun getVerticalSnapPreference(): Int { + return SNAP_TO_START + } + + override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { + return speed / displayMetrics.density + } + + /** + * 缓慢滚动 + */ + fun setScrollSlowly() { + try { + // 建议不同分辨率设备上的滑动速度相同 + speed = context.resources.displayMetrics.density * 0.3f + } catch (e: Exception) { + // do nothing + } + } + + /** + * 快速滚动 + */ + fun setScrollFast() { + try { + speed = context.resources.displayMetrics.density * 0.03f + } catch (e: Exception) { + // do nothing + } + } +} diff --git a/app/src/main/res/drawable/shape_00d9ea_dp6.xml b/app/src/main/res/drawable/shape_00d9ea_dp6.xml new file mode 100644 index 0000000..2f5dfb4 --- /dev/null +++ b/app/src/main/res/drawable/shape_00d9ea_dp6.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_ff9e3a_dp6.xml b/app/src/main/res/drawable/shape_ff9e3a_dp6.xml new file mode 100644 index 0000000..e9e9ec4 --- /dev/null +++ b/app/src/main/res/drawable/shape_ff9e3a_dp6.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_guide_vip.xml b/app/src/main/res/layout/fragment_guide_vip.xml index b6dccfa..dbf1eb9 100644 --- a/app/src/main/res/layout/fragment_guide_vip.xml +++ b/app/src/main/res/layout/fragment_guide_vip.xml @@ -186,7 +186,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" - android:text="43925条" + android:text="0条" android:textSize="@dimen/sp_20" app:gradient_endColor="@color/color_6dd9f2" app:gradient_startColor="@color/white" @@ -231,7 +231,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" - android:text="43925条" + android:text="0条" android:textSize="@dimen/sp_20" app:gradient_endColor="@color/color_6dd9f2" app:gradient_startColor="@color/white" @@ -276,7 +276,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" - android:text="43925条" + android:text="0亿元" android:textSize="@dimen/sp_20" app:gradient_endColor="@color/color_6dd9f2" app:gradient_startColor="@color/white" @@ -321,7 +321,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" - android:text="43925条" + android:text="0条" android:textSize="@dimen/sp_20" app:gradient_endColor="@color/color_6dd9f2" app:gradient_startColor="@color/white" @@ -363,6 +363,37 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + android:layout_height="wrap_content" + android:background="@mipmap/ic_guide_vip_info_bg"> diff --git a/app/src/main/res/layout/layout_x_axis_bar.xml b/app/src/main/res/layout/layout_x_axis_bar.xml new file mode 100644 index 0000000..644392b --- /dev/null +++ b/app/src/main/res/layout/layout_x_axis_bar.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/listitem_bar_chart_x.xml b/app/src/main/res/layout/listitem_bar_chart_x.xml index f8ece8d..ea08645 100644 --- a/app/src/main/res/layout/listitem_bar_chart_x.xml +++ b/app/src/main/res/layout/listitem_bar_chart_x.xml @@ -4,43 +4,25 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - + app:layout_constraintBottom_toTopOf="@id/tv_key" + app:layout_constraintTop_toTopOf="parent" />