简体   繁体   English

Flutter:滚动到 ListView 中的小部件

[英]Flutter: Scrolling to a widget in ListView

How can I scroll to a special widget in a ListView ?如何滚动到ListView中的特殊小部件? For instance I want to scroll automatically to some Container in the ListView if I press a specific button.例如,如果我按下特定按钮,我想自动滚动到ListView中的某个Container

ListView(children: <Widget>[
  Container(...),
  Container(...), #scroll for example to this container 
  Container(...)
]);

By far, the easiest solution is to use Scrollable.ensureVisible(context) .到目前为止,最简单的解决方案是使用Scrollable.ensureVisible(context) As it does everything for you and work with any widget size.因为它可以为您完成所有工作并使用任何小部件大小。 Fetching the context using GlobalKey .使用GlobalKey获取上下文。

The problem is that ListView won't render non-visible items.问题是ListView不会呈现不可见的项目。 Meaning that your target most likely will not be built at all .这意味着你的目标很可能不会在所有建造。 Which means your target will have no context ;这意味着您的目标将没有context preventing you from using that method without some more work.阻止您在没有更多工作的情况下使用该方法。

In the end, the easiest solution will be to replace your ListView by a SingleChildScrollView and wrap your children into a Column .最后,最简单的解决方案是用SingleChildScrollView替换您的ListView并将您的孩子包装成一个Column Example :例子 :

class ScrollView extends StatelessWidget {
  final dataKey = new GlobalKey();

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      primary: true,
      appBar: new AppBar(
        title: const Text('Home'),
      ),
      body: new SingleChildScrollView(
        child: new Column(
          children: <Widget>[
            new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
            new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
            new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
            // destination
            new Card(
              key: dataKey,
              child: new Text("data\n\n\n\n\n\ndata"),
            )
          ],
        ),
      ),
      bottomNavigationBar: new RaisedButton(
        onPressed: () => Scrollable.ensureVisible(dataKey.currentContext),
        child: new Text("Scroll to data"),
      ),
    );
  }
}

NOTE : While this allows to scroll to the desired item easily, consider this method only for small predefined lists.注意:虽然这允许轻松滚动到所需的项目,但仅针对小型预定义列表考虑使用此方法。 As for bigger lists you'll get performance problems.至于更大的列表,你会遇到性能问题。

But it's possible to make Scrollable.ensureVisible work with ListView ;但是可以使Scrollable.ensureVisibleListView although it will require more work.虽然这将需要更多的工作。

Unfortunately, ListView has no built-in approach to a scrollToIndex() function.不幸的是,ListView 没有针对 scrollToIndex() 函数的内置方法。 You'll have to develop your own way to measure to that element's offset for animateTo() or jumpTo() , or you can search through these suggested solutions/plugins or from other posts like flutter ListView scroll to index not available您必须开发自己的方法来测量animateTo()jumpTo()元素的偏移量,或者您可以搜索这些建议的解决方案/插件或从其他帖子(如flutter ListView scroll to index not available)进行搜索

(the general scrollToIndex issue is discussed at flutter/issues/12319 since 2017, but still with no current plans) (自 2017 年以来,在flutter/issues/12319 上讨论了一般的 scrollToIndex 问题,但目前还没有计划)


But there is a different kind of ListView that does support scrollToIndex:但是有一种不同类型的 ListView 确实支持 scrollToIndex:

You set it up exactly like ListView and works the same, except you now have access to a ItemScrollController that does:您可以像 ListView 一样设置它并且工作方式相同,但您现在可以访问ItemScrollController ,它执行以下操作:

  • jumpTo({index, alignment})
  • scrollTo({index, alignment, duration, curve})

Simplified example:简化示例:

ItemScrollController _scrollController = ItemScrollController();

ScrollablePositionedList.builder(
  itemScrollController: _scrollController,
  itemCount: _myList.length,
  itemBuilder: (context, index) {
    return _myList[index];
  },
)

_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));

(note that this library is developed by Google but not by the core Flutter team.) (请注意,这个库是由 Google 开发的,而不是由 Flutter 核心团队开发的。)

If your widgets inside the ListView all have the same height, you can implement it like this:如果 ListView 中的小部件都具有相同的高度,则可以像这样实现它:

Screenshot:截屏:

在此处输入图片说明


For ListView , you can try this, the following code will animate to 10th index.对于ListView ,你可以试试这个,下面的代码将动画到第 10 个索引。

class HomePage extends StatelessWidget {
  final _controller = ScrollController();
  final _height = 100.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _animateToIndex(10),
        child: Icon(Icons.arrow_downward),
      ),
      body: ListView.builder(
        controller: _controller,
        itemCount: 100,
        itemBuilder: (_, i) => Container(
          height: _height,
          child: Card(child: Center(child: Text("Item $i"))),
        ),
      ),
    );
  }

  _animateToIndex(i) => _controller.animateTo(_height * i, duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn);
}

For people are trying to jump to widget in CustomScrollView .对于人们试图跳转到CustomScrollView 中的小部件。 First, add this plugin to your project.首先,将此插件添加到您的项目中。

Then look at my example code below:然后看看我下面的示例代码:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  AutoScrollController _autoScrollController;
  final scrollDirection = Axis.vertical;

  bool isExpaned = true;
  bool get _isAppBarExpanded {
    return _autoScrollController.hasClients &&
        _autoScrollController.offset > (160 - kToolbarHeight);
  }

  @override
  void initState() {
    _autoScrollController = AutoScrollController(
      viewportBoundaryGetter: () =>
          Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
      axis: scrollDirection,
    )..addListener(
        () => _isAppBarExpanded
            ? isExpaned != false
                ? setState(
                    () {
                      isExpaned = false;
                      print('setState is called');
                    },
                  )
                : {}
            : isExpaned != true
                ? setState(() {
                    print('setState is called');
                    isExpaned = true;
                  })
                : {},
      );
    super.initState();
  }

  Future _scrollToIndex(int index) async {
    await _autoScrollController.scrollToIndex(index,
        preferPosition: AutoScrollPosition.begin);
    _autoScrollController.highlight(index);
  }

  Widget _wrapScrollTag({int index, Widget child}) {
    return AutoScrollTag(
      key: ValueKey(index),
      controller: _autoScrollController,
      index: index,
      child: child,
      highlightColor: Colors.black.withOpacity(0.1),
    );
  }

  _buildSliverAppbar() {
    return SliverAppBar(
      brightness: Brightness.light,
      pinned: true,
      expandedHeight: 200.0,
      backgroundColor: Colors.white,
      flexibleSpace: FlexibleSpaceBar(
        collapseMode: CollapseMode.parallax,
        background: BackgroundSliverAppBar(),
      ),
      bottom: PreferredSize(
        preferredSize: Size.fromHeight(40),
        child: AnimatedOpacity(
          duration: Duration(milliseconds: 500),
          opacity: isExpaned ? 0.0 : 1,
          child: DefaultTabController(
            length: 3,
            child: TabBar(
              onTap: (index) async {
                _scrollToIndex(index);
              },
              tabs: List.generate(
                3,
                (i) {
                  return Tab(
                    text: 'Detail Business',
                  );
                },
              ),
            ),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        controller: _autoScrollController,
        slivers: <Widget>[
          _buildSliverAppbar(),
          SliverList(
              delegate: SliverChildListDelegate([
            _wrapScrollTag(
                index: 0,
                child: Container(
                  height: 300,
                  color: Colors.red,
                )),
            _wrapScrollTag(
                index: 1,
                child: Container(
                  height: 300,
                  color: Colors.red,
                )),
            _wrapScrollTag(
                index: 2,
                child: Container(
                  height: 300,
                  color: Colors.red,
                )),
          ])),
        ],
      ),
    );
  }
}

Yeah it's just a example, use your brain to make it this idea become true是的,这只是一个例子,用你的大脑让它成为现实在此处输入图片说明

You can just specify a ScrollController to your listview and call the animateTo method on button click.您只需为您的列表视图指定一个ScrollController并在单击按钮时调用animateTo方法。

A mininmal example to demonstrate animateTo usage :演示animateTo用法的最小示例:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => new _ExampleState();
}

class _ExampleState extends State<Example> {
  ScrollController _controller = new ScrollController();

  void _goToElement(int index){
    _controller.animateTo((100.0 * index), // 100 is the height of container and index of 6th element is 5
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeOut);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(),
      body: new Column(
        children: <Widget>[
          new Expanded(
            child: new ListView(
              controller: _controller,
              children: Colors.primaries.map((Color c) {
                return new Container(
                  alignment: Alignment.center,
                  height: 100.0,
                  color: c,
                  child: new Text((Colors.primaries.indexOf(c)+1).toString()),
                );
              }).toList(),
            ),
          ),
          new FlatButton(
            // on press animate to 6 th element
            onPressed: () => _goToElement(6),
            child: new Text("Scroll to 6th element"),
          ),
        ],
      ),
    );
  }
}

Output:输出:

Use Dependency:使用依赖:

dependencies:
    scroll_to_index: ^1.0.6

Code: (Scroll will always perform 6th index widget as its added below as hardcoded, try with scroll index which you required for scrolling to specific widget)代码:(滚动将始终执行第 6 个索引小部件,因为它在下面添加为硬编码,尝试使用滚动到特定小部件所需的滚动索引)

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final scrollDirection = Axis.vertical;

  AutoScrollController controller;
  List<List<int>> randomList;

  @override
  void initState() {
    super.initState();
    controller = AutoScrollController(
        viewportBoundaryGetter: () =>
            Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
        axis: scrollDirection);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        scrollDirection: scrollDirection,
        controller: controller,
        children: <Widget>[
          ...List.generate(20, (index) {
            return AutoScrollTag(
              key: ValueKey(index),
              controller: controller,
              index: index,
              child: Container(
                height: 100,
                color: Colors.red,
                margin: EdgeInsets.all(10),
                child: Center(child: Text('index: $index')),
              ),
              highlightColor: Colors.black.withOpacity(0.1),
            );
          }),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _scrollToIndex,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
  // Scroll listview to the sixth item of list, scrollling is dependent on this number
  Future _scrollToIndex() async {
    await controller.scrollToIndex(6, preferPosition: AutoScrollPosition.begin);
  }
}

I found a perfect solution to it using ListView .我使用ListView找到了一个完美的解决方案。
I forgot where the solution comes from, so I posted my code.我忘记了解决方案的来源,所以我发布了我的代码。 This credit belongs to other one.这个信用属于另一个信用。

21/09/22:edit. 21/09/22:编辑。 I posted a complete example here, hope it is clearer.我在这里发布了一个完整的例子,希望它更清楚。

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class CScrollToPositionPage extends StatefulWidget {

CScrollToPositionPage();

@override
State<StatefulWidget> createState() => CScrollToPositionPageState();
}

class CScrollToPositionPageState extends State<CScrollToPositionPage> {
static double TEXT_ITEM_HEIGHT = 80;
final _formKey = GlobalKey<FormState>();
late List _controls;
List<FocusNode> _lstFocusNodes = [];

final __item_count = 30;

@override
void initState() {
    super.initState();

    _controls = [];
    for (int i = 0; i < __item_count; ++i) {
        _controls.add(TextEditingController(text: 'hello $i'));

        FocusNode fn = FocusNode();
        _lstFocusNodes.add(fn);
        fn.addListener(() {
            if (fn.hasFocus) {
                _ensureVisible(i, fn);
            }
        });
    }
}

@override
void dispose() {
    super.dispose();

    for (int i = 0; i < __item_count; ++i) {
        (_controls[i] as TextEditingController).dispose();
    }
}

@override
Widget build(BuildContext context) {
    List<Widget> widgets = [];
    for (int i = 0; i < __item_count; ++i) {
        widgets.add(TextFormField(focusNode: _lstFocusNodes[i],controller: _controls[i],));
    }

    return Scaffold( body: Container( margin: const EdgeInsets.all(8),
        height: TEXT_ITEM_HEIGHT * __item_count,
        child: Form(key: _formKey, child: ListView( children: widgets)))
    );
}

Future<void> _keyboardToggled() async {
    if (mounted){
        EdgeInsets edgeInsets = MediaQuery.of(context).viewInsets;
        while (mounted && MediaQuery.of(context).viewInsets == edgeInsets) {
            await Future.delayed(const Duration(milliseconds: 10));
        }
    }

    return;
}
Future<void> _ensureVisible(int index,FocusNode focusNode) async {
    if (!focusNode.hasFocus){
        debugPrint("ensureVisible. has not the focus. return");
        return;
    }

    debugPrint("ensureVisible. $index");
    // Wait for the keyboard to come into view
    await Future.any([Future.delayed(const Duration(milliseconds: 300)), _keyboardToggled()]);


    var renderObj = focusNode.context!.findRenderObject();
    if( renderObj == null ) {
      return;
    }
    var vp = RenderAbstractViewport.of(renderObj);
    if (vp == null) {
        debugPrint("ensureVisible. skip. not working in Scrollable");
        return;
    }
    // Get the Scrollable state (in order to retrieve its offset)
    ScrollableState scrollableState = Scrollable.of(focusNode.context!)!;

    // Get its offset
    ScrollPosition position = scrollableState.position;
    double alignment;

    if (position.pixels > vp.getOffsetToReveal(renderObj, 0.0).offset) {
        // Move down to the top of the viewport
        alignment = 0.0;
    } else if (position.pixels < vp.getOffsetToReveal(renderObj, 1.0).offset){
        // Move up to the bottom of the viewport
        alignment = 1.0;
    } else {
        // No scrolling is necessary to reveal the child
        debugPrint("ensureVisible. no scrolling is necessary");
        return;
    }

    position.ensureVisible(
        renderObj,
        alignment: alignment,
        duration: const Duration(milliseconds: 300),
    );

}

}

Here is the solution for StatefulWidget if you want to made widget visible right after building the view tree .如果您想在构建视图树后立即使小部件可见,这里是StatefulWidget的解决方案。

By extending Remi's answer, you can achieve it with this code:通过扩展Remi 的答案,您可以使用以下代码实现:

class ScrollView extends StatefulWidget {
  // widget init
}

class _ScrollViewState extends State<ScrollView> {

  final dataKey = new GlobalKey();

  // + init state called

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      primary: true,
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: _renderBody(),
    );
  }

  Widget _renderBody() {
    var widget = SingleChildScrollView(
        child: Column(
          children: <Widget>[
           SizedBox(height: 1160.0, width: double.infinity, child: new Card()),
           SizedBox(height: 420.0, width: double.infinity, child: new Card()),
           SizedBox(height: 760.0, width: double.infinity, child: new Card()),
           // destination
           Card(
              key: dataKey,
              child: Text("data\n\n\n\n\n\ndata"),
            )
          ],
        ),
      );
    setState(() {
        WidgetsBinding.instance!.addPostFrameCallback(
              (_) => Scrollable.ensureVisible(dataKey.currentContext!));
    });
    return widget;
  }
}

I am posting a solution here in which List View will scroll 100 pixel right and left .我在这里发布了一个解决方案,其中 List View 将左右滚动 100 像素。 you can change the value according to your requirements.您可以根据您的要求更改该值。 It might be helpful for someone who want to scroll list in both direction对于想要双向滚动列表的人来说可能会有所帮助

import 'package:flutter/material.dart';

class HorizontalSlider extends StatelessWidget {
 HorizontalSlider({Key? key}) : super(key: key);

// Dummy Month name
List<String> monthName = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"July",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
 ];
ScrollController slideController = new ScrollController();

@override
Widget build(BuildContext context) {
 return Container(
  child: Flex(
    direction: Axis.horizontal,
    crossAxisAlignment: CrossAxisAlignment.center,
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      InkWell(
        onTap: () {
          // Here monthScroller.position.pixels represent current postion 
          // of scroller
           slideController.animateTo(
            slideController.position.pixels - 100, // move slider to left
             duration: Duration(
              seconds: 1,
            ),
            curve: Curves.ease,
          );
        },
        child: Icon(Icons.arrow_left),
      ),
      Container(
        height: 50,
        width: MediaQuery.of(context).size.width * 0.7,
        child: ListView(
          scrollDirection: Axis.horizontal,
          controller: slideController,
          physics: ScrollPhysics(),
          children: monthName
              .map((e) => Padding(
                    padding: const EdgeInsets.all(12.0),
                    child: Text("$e"),
                  ))
              .toList(),
        ),
      ),
      GestureDetector(
        onTap: () {
          slideController.animateTo(
            slideController.position.pixels +
                100, // move slider 100px to right
            duration: Duration(
              seconds: 1,
            ),
            curve: Curves.ease,
          );
        },
        child: Icon(Icons.arrow_right),
      ),
    ],
  ),
);
 }
 }

You can use GlobalKey to access buildercontext.您可以使用 GlobalKey 访问 buildercontext。

I use GlobalObjectKey with Scrollable .我将GlobalObjectKeyScrollable一起使用。

Define GlobalObjectKey in item of ListViewListView的 item 中定义 GlobalObjectKey

ListView.builder(
itemCount: category.length,
itemBuilder: (_, int index) {
return Container(
    key: GlobalObjectKey(category[index].id),

You can navigate to item from anywhere您可以从任何地方导航到项目

InkWell(
  onTap: () {
Scrollable.ensureVisible(GlobalObjectKey(category?.id).currentContext);

You add scrollable animation changing property of ensureVisible您添加了 ensureVisible 的可滚动动画更改属性

Scrollable.ensureVisible(
  GlobalObjectKey(category?.id).currentContext,
  duration: Duration(seconds: 1),// duration for scrolling time
  alignment: .5, // 0 mean, scroll to the top, 0.5 mean, half
  curve: Curves.easeInOutCubic);

You can also simply use the FixedExtentScrollController for same size items with the index of your initialItem :您也可以简单地将FixedExtentScrollController用于具有您的initialItem索引的相同大小的项目:

controller: FixedExtentScrollController(initialItem: itemIndex);

The documentation : Creates a scroll controller for scrollables whose items have the same size.文档:为项目具有相同大小的可滚动项创建一个滚动控制器。

The simplest way is to call this method inside your InitState method.最简单的方法是在 InitState 方法中调用此方法。 (not the build to evict unwanted errors) (不是用于驱逐不需要的错误的构建)

WidgetsBinding.instance.addPostFrameCallback((_) => Scrollable.ensureVisible(targetKey.currentContext!))

WidgetsBinding.instance.addPostFrameCallback will guarantee that the list is builded and the this automatic search for your target and move the scroll to it. WidgetsBinding.instance.addPostFrameCallback将保证构建列表并自动搜索您的目标并将滚动条移动到它。 You can then customize the animation of the scroll effect on the Scrollable.ensureVisible method然后可以在Scrollable.ensureVisible方法上自定义滚动效果的animation

Note: Remember to add the targetKey (a GlobalKey ) to the widget you want to scroll to.注意:请记住将targetKey (一个GlobalKey )添加到您要滚动到的小部件。

Adding with Rémi Rousselet's answer,加上 Rémi Rousselet 的回答,

If there is a case you need to scroll past to end scroll position with addition of keyboard pop up, this might be hided by the keyboard .如果您需要通过添加键盘弹出窗口滚动过去以结束滚动位置,这可能会被键盘隐藏。 Also you might notice the scroll animation is a bit inconsistent when keyboard pops up (there is addition animation when keyboard pops up), and sometimes acts weird.此外,您可能会注意到键盘弹出时滚动动画有点不一致键盘弹出时有附加动画),有时表现得很奇怪。 In that case wait till the keyboard finishes animation(500ms for ios).在这种情况下,请等待键盘完成动画(ios 为 500 毫秒)。

BuildContext context = key.currentContext;
  Future.delayed(const Duration(milliseconds: 650), () {
    Scrollable.of(context).position.ensureVisible(
        context.findRenderObject(),
        duration: const Duration(milliseconds: 600));
  });
  1. To achieve initial scrolling at a particular index in a list of items在项目列表中的特定索引处实现初始滚动
  2. on tap of the floating action button you will be scrolled to an index of 10 in a list of items点击浮动操作按钮,您将滚动到项目列表中的索引 10
    class HomePage extends StatelessWidget {
      final _controller = ScrollController();
      final _height = 100.0;
    
      @override
      Widget build(BuildContext context) {
        
        // to achieve initial scrolling at particular index
        SchedulerBinding.instance.addPostFrameCallback((_) {
          _scrollToindex(20);
        });
    
        return Scaffold(
          appBar: AppBar(),
          floatingActionButton: FloatingActionButton(
            onPressed: () => _scrollToindex(10),
            child: Icon(Icons.arrow_downward),
          ),
          body: ListView.builder(
            controller: _controller,
            itemCount: 100,
            itemBuilder: (_, i) => Container(
              height: _height,
              child: Card(child: Center(child: Text("Item $i"))),
            ),
          ),
        );
      }
    // on tap, scroll to particular index
      _scrollToindex(i) => _controller.animateTo(_height * i,
          duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn);
    }

This solution improves upon other answers as it does not require hard-coding each elements' heights.该解决方案改进了其他答案,因为它不需要对每个元素的高度进行硬编码。 Adding ScrollPosition.viewportDimension<\/code> and ScrollPosition.maxScrollExtent<\/code> yields the full content height.添加ScrollPosition.viewportDimension<\/code>和ScrollPosition.maxScrollExtent<\/code>会产生完整的内容高度。 This can be used to estimate the position of an element at some index.这可用于估计元素在某个索引处的位置。 If all elements are the same height, the estimation is perfect.如果所有元素的高度相同,则估计是完美的。

// Get the full content height.
final contentSize = controller.position.viewportDimension + controller.position.maxScrollExtent;
// Index to scroll to.
final index = 100;
// Estimate the target scroll position.
final target = contentSize * index / itemCount;
// Scroll to that position.
controller.position.animateTo(
  target,
  duration: const Duration(seconds: 2),
  curve: Curves.easeInOut,
);

这些答案不会使您的列表默认集中在底部,我使用了这个技巧希望它有所帮助:首先反转您提供给ListView.builder的列表,然后将此属性添加到您的 ListView : reverse: true

Simply use page view controller.只需使用页面视图控制器。 Example:例子:

   var controller = PageController();  
     
    ListView.builder(
      controller: controller,
      itemCount: 15,
      itemBuilder: (BuildContext context, int index) {
       return children[index);
      },
    ),
     ElevatedButton(
            onPressed: () {
              controller.animateToPage(5,   //any index that you want to go
    duration: Duration(milliseconds: 700), curve: Curves.linear);
              },
            child: Text(
              "Contact me",), 

       

您可以在加载完成后使用 controller.jumpTo(100)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM