arkts鸿蒙实现基金、股票列表,纵向横向滑动,联动列表的开发

380 阅读3分钟

写这篇文章为了以后少踩坑,基于List + scroll 实现了基金列表的横向滚动+竖向滑动, 参考资料:

// 参考    https://blog.csdn.net/HarmonyOSMN/article/details/140107235
//参考  https://ost.51cto.com/answer/11479

该项目拉取的是利得基金的基金列表数据:链接 www.leadfund.com.cn/html5/index… 效果如下图

image.png

数据格式

image.png image.png



export interface ProductPage{
  productPage:ProductPageBean

}
export interface ProductPageBean{


  list:ProductPageFundInfo[]
}

export interface ProductPageFundInfo{

  follow:boolean
  fundCode:string
  fundName:string
  fundType:string

  dayRateValue:number
  dayRateValueFormat:string

  nav:number
  navFormat:string

  navDayFormat:string

  recentlyMonthRateValue:number
  recentlyMonthRateValueFormat:string

  recentlySixMonthRateValue:number
  recentlySixMonthRateValueFormat:string

  recentlyThreeMonthRateValue:number
  recentlyThreeMonthRateValueFormat:string

  recentlyWeekRateValue:number
  recentlyWeekRateValueFormat:string

  recentlyYearRateValue:number
  recentlyYearRateValueFormat:string

  recentlyTwoYearRateValue:number
  recentlyTwoYearRateValueFormat:string

  recentlyThreeYearRateValue:number
  recentlyThreeYearRateValueFormat:string

  recentlyFiveYearRateValue:number
  recentlyFiveYearRateValueFormat:string


  setUpRateValue:number
  setUpRateValueFormat:string

  thisYearRateValue:number
  thisYearRateValueFormat:string


}


export class   LabelBean{
  value:string
  label:string
  key:string


}
import http from '@ohos.net.http'
import axios from '@ohos/axios'
import ArrayList from '@ohos.util.ArrayList'
import { FundApi} from '../../api/Fund'
import { ItemStyle, TIME } from '../../common/constants/Constants'
import { LogBus } from '../../utils/LogBus'
import { ZZGTitle } from '../../view/pub/ZZGTitle'
import { BaseResponse } from '../../viewmodel/base/BaseResponse'
import { LabelBean } from '../../viewmodel/lead/LabelBean'
import { ProductTypeData } from '../../viewmodel/lead/ProductTypeData'
import { ProductPage, ProductPageFundInfo } from '../../viewmodel/lead/ProductPage'
import { ProductFundListItem } from '../../view/lead/fund/ProductFundListItem'
import ProductPageParams from '../../viewmodel/lead/ProductPageParams'

const tag: string = 'FundListPages request____'

@Entry
@Component
export struct FundListPages {
  //基金类型  全部,股票,债券。。。
  @State productTypeList: LabelBean[] = []
  productType?: ProductTypeData | undefined
  @State productTypePos: number = 0

  //排序方式
  @State increaseList: LabelBean[] = []
  // @State productTypeSize: number = 0

  @State fundInfoList: ProductPageFundInfo[] = []
  private productPageParams :ProductPageParams = new ProductPageParams("","","","","",1,1)


  horizontalScroller: Scroller = new Scroller()


  //获取基金类型
  async getProductTypeData() {
    const res = await FundApi.getProductType()
    LogBus.d(tag, "getProductTypeData 完成")
    this.productType = res
    if (this.productType == null) {
      return
    }
    this.productTypeList = this.productType.productTypeList
    LogBus.d(tag, "productTypeList.size = " + this.productTypeList.length)

    if (this.productTypeList.length > 0) {

      /**
       * data.fundType: -1
       data.orderBy: 2
       data.rateValueType: -1
       data.rateValueTypeWrapper: -1
       page.limit: 20
       page.page: 1
       */

      let label: LabelBean = this.productTypeList[0]
      this.productPageParams.fundType = label.value
      this.productPageParams.orderBy = this.productType.orderList[0].value
      this.productPageParams.rateValueType = '-1'
      this.productPageParams.rateValueTypeWrapper = '-1'
      this.productPageParams.page = 1




      this.getProductList()


      let labelIncrease: LabelBean = new LabelBean()
      labelIncrease.value = '-1'
      labelIncrease.label = '最新净值'
      labelIncrease.key = 'dayRateValueFormat'

      if (this.productType != null) {
        this.increaseList = this.productType.increaseList

        this.increaseList.unshift(labelIncrease)
      }

    }



  }


  /**
   * 获取基金列表
   */
  async getProductList() {
    const res = await FundApi.getProductPage(this.productPageParams)


    let productPage: ProductPage = res
    this.fundInfoList = productPage.productPage.list
    LogBus.d(tag, "getProductList fundInfoList 长度为"+ this.fundInfoList.length)

  }

  aboutToAppear() {
    LogBus.d(tag, 'getProductTypeData() 执行')
    this.getProductTypeData()
  }

  build() {

    Column() {
      ZZGTitle({ title: '基金' })
      Tabs({ barPosition: BarPosition.Start }) {

        ForEach(this.productTypeList, (item: LabelBean) => {
          TabContent() {
            // ProductFundListItem({fundInfoList:this.fundInfoList})

            Column() {
            }
            .alignItems(HorizontalAlign.Start)

          }.tabBar(item.label)
          .width(120)
          .height('100%')

        })
      }

      .barMode(BarMode.Scrollable)
      .vertical(false)
      .width('100%')
      .height(60)
      .onChange(index => {
        this.productTypePos = index
      })
      ProductFundListItem({fundInfoList:$fundInfoList,increaseList:$increaseList})
    }.width('100%')
    .height('100%')
  }

  @Builder TabBuilder(item: LabelBean) {
    Row() {
      Text(item.label)
      // if (item.key === this.productPageParams.rateValueTypeWrapper) {
      //   Image($r('app.media.bg_my_topred'))
      //     .backgroundColor(Color.Red)
      //     .width(50)
      //     .height(50)
      //
      // } else {
      //   Image($r('app.media.img_setting'))
      //     // Image($r('app.media.ic_public_back'))
      //     .width(50)
      //     .height(50)
      // }

    }.width(ItemStyle.FUND_TAB_WIDTH)

  }
}

ZZGTitle是我写的title,大家换成自己的title就好了。接口数据自己去利得基金网站拿吧,


import { ItemStyle } from '../../../common/constants/Constants'
import { LogBus } from '../../../utils/LogBus';
import { LabelBean } from '../../../viewmodel/lead/LabelBean';
import { ProductPageFundInfo } from '../../../viewmodel/lead/ProductPage'

@Component
export struct ProductFundListItem {
  // private fundInfo: ProductPageFundInfo
  @Link fundInfoList: ProductPageFundInfo[];
  @Link increaseList: LabelBean[]
  @State remainOffset: number = 0 // 内容行在横向滚动时回调的offset
  private bottomRightScroller: Scroller = new Scroller() //下部分左侧标题List(行标题)
  private bottomLeftScroller: Scroller = new Scroller() // 下部分右侧内容List(内容)
  private topRightScroller: Scroller = new Scroller() // 上部分右侧类型List(列标题)
  private bottomRightHorizontalScroller: Scroller = new Scroller() // 右下部的横向滑动
  @State rightHeaderOffset: number = 0






  /**
   * 是否设置阴影
   */
  @State isShowShadow:boolean = false
  // hScroller: Scroller = new Scroller()
  /**
   * 基金列表各项宽度
   */
  childItemWidth: number = 70
  LeftItemWidth = 150
  listItemHeight: number = 60

  build() {

    Column() {
      this.topFixed()
      Line().height(0.5).width('100%').backgroundColor('#EEEEEE')
      Row() {

        this.leftList()
        // this.rightList()
        this.rightScroll()
      }

    }

  }

  getRateShowText(rateFormat: string): string {
    if (rateFormat == null || rateFormat == "") {
      return '--'
    }
    return rateFormat
  }

  @Builder
  topFixed() {

    Row() {

      // 上部分左侧固定信息

      Row() {
        Text('名称/代码')
          .fontColor(ItemStyle.TEXT_BLACK_2)
          .fontSize(14)
          .width(this.LeftItemWidth)
          .textAlign(TextAlign.Start)


        Scroll(this.topRightScroller) {
          Row() {
            ForEach(this.increaseList, (item: LabelBean) => {

              Text(item.label)
                .fontColor(ItemStyle.TEXT_BLACK_2)
                .fontSize(14)
                .width(this.childItemWidth)
                .textAlign(TextAlign.Start)

            })
          }
        }.shadow( { radius: 10, color: Color.Gray })
        .scrollBar(BarState.Off)
        .width(this.childItemWidth * 12)
        // .position({ x: this.LeftItemWidth, y: 0 })
        .onScroll((scrollOffset: number, scrollState: ScrollState) => {
          console.log('fxm' + scrollOffset)
          this.rightHeaderOffset = this.topRightScroller.currentOffset().xOffset
          this.bottomRightHorizontalScroller.scrollTo({ xOffset: this.topRightScroller.currentOffset().xOffset, yOffset: 0 })
        })
        .edgeEffect(EdgeEffect.None) // 边缘效果设置为Spring
        .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
        .scrollable(ScrollDirection.Horizontal)
        .padding({ top: 0 })

      }

    }.height(this.listItemHeight).width('100%')

  }

  @Builder
  leftList() {
    List({ scroller: this.bottomLeftScroller }) {
      ForEach(this.fundInfoList, (item: ProductPageFundInfo) => {
        ListItem() {
          Column() {
            Text(item.fundName)
              .fontColor(ItemStyle.TEXT_BLACK_2)
              .fontSize(14)
              .maxLines(1)
              .textAlign(TextAlign.Start)

            Text(item.fundCode)
              .fontColor(ItemStyle.TEXT_BLACK_2)
              .fontSize(14)
              .maxLines(1)
              .textAlign(TextAlign.Start)
          }
          .width(this.LeftItemWidth)
          .alignItems(HorizontalAlign.Start)

        }.height(this.listItemHeight)

      })
    }
    .width(this.LeftItemWidth)
    .divider({ color: ItemStyle.TEXT_GRAY_1,
      strokeWidth: 0.5 })
    .scrollBar(BarState.Off)
    .edgeEffect(EdgeEffect.None)
    // .onScroll((scrollOffset: number, scrollState: ScrollState) => {
    //   this.bottomRightScroller.scrollTo({ xOffset: this.remainOffset, yOffset: scrollOffset })
    //
    // })
    .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
    .onScrollFrameBegin((offset: number, state: ScrollState) => {
      this.bottomRightScroller.scrollTo({
        xOffset: 0,
        yOffset: this.bottomLeftScroller.currentOffset().yOffset + offset,
        animation: false
      })
      return { offsetRemain: offset }
    })
  }

  @Builder
  rightScroll() {

    Scroll(this.bottomRightHorizontalScroller) {
      List({ initialIndex: 0, scroller: this.bottomRightScroller }) {
        ForEach(this.fundInfoList, (item: ProductPageFundInfo, index: number) => {
          // ListItemGroup({ header: this.rightStickyHeader(index) }) {
          ListItem() {
            Row() {

              //最新净值
              Text(item.navFormat)
                .fontColor(ItemStyle.TEXT_BLACK_2)
                .fontSize(14)
                .width(this.childItemWidth)
              //近一日
              Text(this.getRateShowText(item.dayRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.dayRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //近一周
              Text(this.getRateShowText(item.recentlyWeekRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyWeekRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)
              //近一月
              Text(this.getRateShowText(item.recentlyMonthRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyMonthRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)
              //近三月
              Text(this.getRateShowText(item.recentlyThreeMonthRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyThreeMonthRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //近六月
              Text(this.getRateShowText(item.recentlySixMonthRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlySixMonthRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)
              //近一年
              Text(this.getRateShowText(item.recentlyYearRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyYearRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //近两年
              Text(this.getRateShowText(item.recentlyTwoYearRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyTwoYearRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //近三年
              Text(this.getRateShowText(item.recentlyThreeYearRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyThreeYearRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //近五年
              Text(this.getRateShowText(item.recentlyFiveYearRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.recentlyFiveYearRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //今年以来
              Text(this.getRateShowText(item.thisYearRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.thisYearRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

              //成立以来
              Text(this.getRateShowText(item.setUpRateValueFormat))
                .width(this.childItemWidth)
                .fontColor(item.setUpRateValue >= 0 ? ItemStyle.RED_RATE : ItemStyle.TEXT_BLACK_2)

            }
            .height(this.listItemHeight)

          }

        })
      }
      .divider({ color: ItemStyle.TEXT_GRAY_1,
        strokeWidth: 0.5 })
      .sticky(StickyStyle.Header)
      .listDirection(Axis.Vertical)
      .scrollBar(BarState.Off)
      .friction(0.6)
      .edgeEffect(EdgeEffect.None)
      .width('100%')
      .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
      .onScrollFrameBegin((offset: number, state: ScrollState) => {
        this.bottomLeftScroller.scrollTo({
          xOffset: 0,
          yOffset: this.bottomRightScroller.currentOffset().yOffset + offset,
          animation: false
        })
        return { offsetRemain: offset }
      })
    }
    .width(this.childItemWidth * 12)
    // .position({ x: this.LeftItemWidth, y: 0 })
    .onScroll((scrollOffset: number, scrollState: ScrollState) => {
      console.log('fxm' + scrollOffset)
      this.rightHeaderOffset = this.bottomRightHorizontalScroller.currentOffset().xOffset
      LogBus.d("ProductFundListItem","rightHeaderOffset = "+ this.rightHeaderOffset )
      if ( this.rightHeaderOffset !=null) {
        //添加阴影

      }
      this.topRightScroller.scrollTo({ xOffset: this.bottomRightHorizontalScroller.currentOffset().xOffset, yOffset: 0 })
    })
    .edgeEffect(EdgeEffect.None) // 边缘效果设置为Spring
    .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
    .scrollable(ScrollDirection.Horizontal)
    .padding({ top: 0 })

  }
}

核心代码是几个Scroller控制器的关联

image.png

image.png

image.png

其实还是比较简单的