经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android中FlowLayout组件实现瀑布流效果
来源:jb51  时间:2022/1/19 19:14:57  对本文有异议

纸上得来终觉浅,绝知此事要躬行。

动手实践是学习的最好的方式,对于自定义View来说,听和看只能是过一遍流程,能掌握个30%、40%就不错了,而且很快就会遗忘,想变成自己的东西必须动手来写几遍,细细体会其中的细节和系统API的奥秘、真谛。

进入主题,今天来手写一个瀑布流组件FlowLayout,温习下自定义view的流程和关键点,先来张效果图

FlowLayout实现关键步骤:

1、创建一个view继承自ViewGroup

  1. class ZSFlowLayout : ViewGroup {
  2. constructor(context: Context) : super(context) {}
  3. /**
  4. * 必须的构造函数,系统会通过反射来调用此构造方法完成view的创建
  5. */
  6. constructor(context: Context, attr: AttributeSet) : super(context, attr) {}
  7. constructor (context: Context, attr: AttributeSet, defZStyle: Int) : super(
  8. context,
  9. attr,
  10. defZStyle
  11. ) {
  12. }
  13. }

  这里注意两个参数的构造函数是必须的构造函数,系统会通过反射来调用此构造方法完成view的创建,具体调用位置在LayoutInflater 的 createView方法中,如下(基于android-31):

省略了若干不相关代码,并写了重要的注释信息,请留意

  1. public final View createView(@NonNull Context viewContext, @NonNull String name,
  2. @Nullable String prefix, @Nullable AttributeSet attrs)
  3. throws ClassNotFoundException, InflateException {
  4. Objects.requireNonNull(viewContext);
  5. Objects.requireNonNull(name);
  6. //从缓存中取对应的构造函数
  7. Constructor<? extends View> constructor = sConstructorMap.get(name);
  8. Class<? extends View> clazz = null;
  9. try {
  10. if (constructor == null) {
  11. // 通过反射创建class对象
  12. clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
  13. mContext.getClassLoader()).asSubclass(View.class);
  14. //创建构造函数 这里的mConstructorSignature 长这个样子
  15. //static final Class<?>[] mConstructorSignature = new Class[] {
  16. // Context.class, AttributeSet.class};
  17. //看到了没 就是我们第二个构造方法
  18. constructor = clazz.getConstructor(mConstructorSignature);
  19. constructor.setAccessible(true);
  20. //缓存构造方法
  21. sConstructorMap.put(name, constructor);
  22. } else {
  23. ...
  24. }
  25. try {
  26. //执行构造函数 创建出view
  27. final View view = constructor.newInstance(args);
  28. ...
  29. return view;
  30. } finally {
  31. mConstructorArgs[0] = lastContext;
  32. }
  33. } catch (Exception e) {
  34. ...
  35. } finally {
  36. ...
  37. }
  38. }

 对LayoutInflater以及setContentView、DecorView、PhoneWindow相关一整套源码流程感兴趣的可以看下我这篇文章:

Activity setContentView背后的一系列源码分析

2、重写并实现onMeasure方法

  1. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  2. }

(1)先了解下 MeasureSpec的含义

MeasureSpec是View中的内部类,基本都是二进制运算。由于int是32位的,用高两位表示mode,低30位表示size。

(2)重点解释下 两个参数widthMeasureSpec 和 heightMeasureSpec是怎么来的

这个是父类传给我们的尺寸规则,那父类是如何按照什么规则生成的widthMeasureSpec、heightMeasureSpec呢?

答:父类会结合自身的情况,并且结合子view的情况(子类的宽是match_parent、wrap_content、还是写死的值)来生成的。生成的具体逻辑 请见:ViewGroup的getChildMeasureSpec方法

相关说明都写在了注释中,请注意查看:

  1. /**
  2. * 这里的spec、padding是父类的尺寸规则,childDimension是子类的尺寸
  3. * 举个例子,如果我们写的FlowLayout被LinearLayout包裹,那这里spec、padding就是LinearLayout的
  4. * spec 可以是widthMeasureSpec 也可以是 heightMeasureSpec 宽和高是分开计算的,childDimension
  5. * 则是我们在布局文件中对FlowLayout设置的对应的宽、高
  6. */
  7. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  8. //获取父类的尺寸模式
  9. int specMode = MeasureSpec.getMode(spec);
  10. //获取父类的尺寸大小
  11. int specSize = MeasureSpec.getSize(spec);
  12. //去掉padding后的大小 最小不能低于0
  13. int size = Math.max(0, specSize - padding);
  14. int resultSize = 0;
  15. int resultMode = 0;
  16. switch (specMode) {
  17. // 如果父类的模式是MeasureSpec.EXACTLY(精确模式,父类的值是可以确定的)
  18. case MeasureSpec.EXACTLY:
  19. if (childDimension >= 0) {
  20. //此时子view的大小就是我们设置的值,超过父类也没事,开发人员自定义设置的
  21. //比如父view的宽是100dp,子view宽你非要设置200dp,那就给200dp,这么做有什么
  22. //意义?这样是可以扩展的,不至于限制死,比如子view可能具有滚动属性或者其他高级
  23. //玩法
  24. resultSize = childDimension;
  25. resultMode = MeasureSpec.EXACTLY;
  26. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  27. // MATCH_PARENT 则子view和父view大小一致 模式是确定的
  28. resultSize = size;
  29. resultMode = MeasureSpec.EXACTLY;
  30. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  31. // WRAP_CONTENT 则子view和父view大小一致 模式是最大不超过这个值
  32. resultSize = size;
  33. resultMode = MeasureSpec.AT_MOST;
  34. }
  35. break;
  36. // Parent has imposed a maximum size on us
  37. case MeasureSpec.AT_MOST:
  38. if (childDimension >= 0) {
  39. // 按子view值执行,确定模式
  40. resultSize = childDimension;
  41. resultMode = MeasureSpec.EXACTLY;
  42. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  43. //按父view值执行 模式是最多不超过指定值模式
  44. resultSize = size;
  45. resultMode = MeasureSpec.AT_MOST;
  46. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  47. //按父view值执行 模式是最多不超过指定值模式
  48. resultSize = size;
  49. resultMode = MeasureSpec.AT_MOST;
  50. }
  51. break;
  52. // Parent asked to see how big we want to be
  53. case MeasureSpec.UNSPECIFIED:
  54. if (childDimension >= 0) {
  55. // 按子view值执行,确定模式
  56. resultSize = childDimension;
  57. resultMode = MeasureSpec.EXACTLY;
  58. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  59. // 按父view值执行 模式是未定义
  60. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  61. resultMode = MeasureSpec.UNSPECIFIED;
  62. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  63. // 按父view值执行 模式是未定义
  64. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  65. resultMode = MeasureSpec.UNSPECIFIED;
  66. }
  67. break;
  68. }
  69. //noinspection ResourceType
  70. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  71. }

其实就是网上的这张图

3、重写并实现onLayout方法

我们要在这个方法里面,确定所有被添加到我们的FlowLayout里面的子view的位置,这里没有特殊要注意的地方,控制好细节就可以。

三个关键步骤介绍完了,下面上实战代码:

ZSFlowLayout:

  1. /**
  2. * 自定义瀑布流布局 系统核心方法
  3. * ViewGroup getChildMeasureSpec 获取子view的MeasureSpec信息
  4. * View measure 对view进行测量 测量以后就知道view大小了 之后可以通过getMeasuredWidth、getMeasuredHeight来获取其宽高
  5. * View MeasureSpec.getMode 获取宽或高的模式(MeasureSpec.EXACTLY、MeasureSpec.AT_MOST、MeasureSpec.UNSPECIFIED)
  6. * View MeasureSpec.getSize 获取父布局能给我们的宽、高大小
  7. * View setMeasuredDimension 设置测量结果
  8. * View layout(left,top,right,bottom) 设置布局位置
  9. *
  10. * 几个验证点 getMeasuredHeight、getHeight何时有值 结论:分别在onMeasure 和 onLayout之后
  11. * 子view是relativeLayout 并有子view时的情况 没问题
  12. * 通过addView方式添加 ok 已验证
  13. */
  14. class ZSFlowLayout : ViewGroup {
  15. //保存所有子view 按行保存 每行都可能有多个view 所有是一个list
  16. var allViews: MutableList<MutableList<View>> = mutableListOf()
  17. //每个子view之间的水平间距
  18. val horizontalSpace: Int =
  19. resources.getDimensionPixelOffset(R.dimen.zs_flowlayout_horizontal_space)
  20. //每行之间的间距
  21. val verticalSpace: Int = resources.getDimensionPixelOffset(R.dimen.zs_flowlayout_vertical_space)
  22. //记录每一行的行高 onLayout时会用到
  23. var lineHeights: MutableList<Int> = mutableListOf()
  24. constructor(context: Context) : super(context) {}
  25. /**
  26. * 必须的构造函数,系统会通过反射来调用此构造方法完成view的创建
  27. */
  28. constructor(context: Context, attr: AttributeSet) : super(context, attr) {}
  29. constructor (context: Context, attr: AttributeSet, defZStyle: Int) : super(
  30. context,
  31. attr,
  32. defZStyle
  33. ) {
  34. }
  35. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  36. //会测量次
  37. allViews.clear()
  38. lineHeights.clear()
  39. //保存每一行的view
  40. var everyLineViews: MutableList<View> = mutableListOf()
  41. //记录每一行当前的宽度,用来判断是否要换行
  42. var curLineHasUsedWidth: Int = paddingLeft + paddingRight
  43. //父布局能给的宽
  44. val selfWidth: Int = MeasureSpec.getSize(widthMeasureSpec)
  45. //父布局能给的高
  46. val selfHeight: Int = MeasureSpec.getSize(heightMeasureSpec)
  47. //我们自己通过测量需要的宽(如果用户在布局里对ZSFlowLayout的宽设置了wrap_content 就会用到这个)
  48. var selfNeedWidth = 0
  49. //我们自己通过测量需要的高(如果用户在布局里对ZSFlowLayout的高设置了wrap_content 就会用到这个)
  50. var selfNeedHeight = paddingBottom + paddingTop
  51. var curLineHeight = 0
  52. //第一步 先测量子view 核心系统方法是 View measure方法
  53. //(1)因为子view有很多,所以循环遍历执行
  54. for (i in 0 until childCount) {
  55. val childView = getChildAt(i)
  56. if (childView.visibility == GONE) {
  57. continue
  58. }
  59. //测量view之前 先把测量需要的参数准备好 通过ViewGroup getChildMeasureSpec获取子view的MeasureSpec信息
  60. val childWidthMeasureSpec = getChildMeasureSpec(
  61. widthMeasureSpec,
  62. paddingLeft + paddingRight,
  63. childView.layoutParams.width
  64. )
  65. val childHeightMeasureSpec = getChildMeasureSpec(
  66. heightMeasureSpec,
  67. paddingTop + paddingBottom,
  68. childView.layoutParams.height
  69. )
  70. //调用子view的measure方法来对子view进行测量
  71. childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
  72. //测量之后就能拿到子view的宽高了,保存起来用于判断是否要换行 以及需要的总高度
  73. val measuredHeight = childView.measuredHeight
  74. val measuredWidth = childView.measuredWidth
  75. //按行保存view 保存之前判断是否需要换行,如果需要就保存在下一行的list里面
  76. if (curLineHasUsedWidth + measuredWidth > selfWidth) {
  77. //要换行了 先记录换行之前的数据
  78. lineHeights.add(curLineHeight)
  79. selfNeedHeight += curLineHeight + verticalSpace
  80. allViews.add(everyLineViews)
  81. //再处理当前要换行的view相关数据
  82. curLineHeight = measuredHeight
  83. everyLineViews = mutableListOf()
  84. curLineHasUsedWidth = paddingLeft + paddingRight + measuredWidth + horizontalSpace
  85. } else {
  86. //每一行的高度是这一行view中最高的那个
  87. curLineHeight = curLineHeight.coerceAtLeast(measuredHeight)
  88. curLineHasUsedWidth += measuredWidth + horizontalSpace
  89. }
  90. everyLineViews.add(childView)
  91. selfNeedWidth = selfNeedWidth.coerceAtLeast(curLineHasUsedWidth)
  92. //处理最后一行
  93. if (i == childCount - 1) {
  94. curLineHeight = curLineHeight.coerceAtLeast(measuredHeight)
  95. allViews.add(everyLineViews)
  96. selfNeedHeight += curLineHeight
  97. lineHeights.add(curLineHeight)
  98. }
  99. }
  100. //第二步 测量自己
  101. //根据父类传入的尺寸规则 widthMeasureSpec、heightMeasureSpec 获取当前自身应该遵守的布局模式
  102. //以widthMeasureSpec为例说明下 这个是父类传入的,那父类是如何按照什么规则生成的widthMeasureSpec呢?
  103. //父类会结合自身的情况,并且结合子view的情况(子类的宽是match_parent、wrap_content、还是写死的值)来生成
  104. //生成的具体逻辑 请见:ViewGroup的getChildMeasureSpec方法
  105. //(1)获取父类传过来的 我们自身应该遵守的尺寸模式
  106. val widthMode = MeasureSpec.getMode(widthMeasureSpec)
  107. val heightMode = MeasureSpec.getMode(heightMeasureSpec)
  108. //(2)根据模式来判断最终的宽高
  109. val widthResult = if (widthMode == MeasureSpec.EXACTLY) selfWidth else selfNeedWidth
  110. val heightResult = if (heightMode == MeasureSpec.EXACTLY) selfHeight else selfNeedHeight
  111. //第三步 设置自身的测量结果
  112. setMeasuredDimension(widthResult, heightResult)
  113. }
  114. override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
  115. //设置所有view的位置
  116. var curT = paddingTop
  117. for (i in allViews.indices) {
  118. val mutableList = allViews[i]
  119. //记录每一行view的当前距离父布局左侧的位置 初始值就是父布局的paddingLeft
  120. var curL = paddingLeft
  121. if (i != 0) {
  122. curT += lineHeights[i - 1] + verticalSpace
  123. }
  124. for (j in mutableList.indices) {
  125. val view = mutableList[j]
  126. val right = curL + view.measuredWidth
  127. val bottom = curT + view.measuredHeight
  128. view.layout(curL, curT, right, bottom)
  129. //为下一个view做准备
  130. curL += view.measuredWidth + horizontalSpace
  131. }
  132. }
  133. }
  134. }

在布局文件中使用:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent">
  5. <LinearLayout
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:orientation="vertical">
  9. <TextView
  10. android:layout_marginTop="10dp"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:layout_marginLeft="@dimen/zs_flowlayout_title_marginL"
  14. android:text="三国名将"
  15. android:textColor="@android:color/black"
  16. android:textSize="18sp" />
  17. <com.zs.test.customview.ZSFlowLayout
  18. android:id="@+id/activity_flow_flowlayout"
  19. android:layout_width="match_parent"
  20. android:layout_height="wrap_content"
  21. android:layout_margin="8dp"
  22. android:padding="7dp">
  23. <TextView
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:background="@drawable/shape_button_circular"
  27. android:text="吕布吕奉先" />
  28. <TextView
  29. android:layout_width="wrap_content"
  30. android:layout_height="wrap_content"
  31. android:background="@drawable/shape_button_circular"
  32. android:text="赵云赵子龙" />
  33. <TextView
  34. android:layout_width="wrap_content"
  35. android:layout_height="wrap_content"
  36. android:background="@drawable/shape_button_circular"
  37. android:paddingLeft="10dp"
  38. android:text="典韦" />
  39. <TextView
  40. android:layout_width="wrap_content"
  41. android:layout_height="wrap_content"
  42. android:background="@drawable/shape_button_circular"
  43. android:text="关羽关云长" />
  44. <TextView
  45. android:layout_width="wrap_content"
  46. android:layout_height="wrap_content"
  47. android:background="@drawable/shape_button_circular"
  48. android:text="马超马孟起" />
  49. <TextView
  50. android:layout_width="wrap_content"
  51. android:layout_height="wrap_content"
  52. android:background="@drawable/shape_button_circular"
  53. android:text="张飞张翼德" />
  54. <TextView
  55. android:layout_width="wrap_content"
  56. android:layout_height="wrap_content"
  57. android:background="@drawable/shape_button_circular"
  58. android:text="黄忠" />
  59. <TextView
  60. android:layout_width="wrap_content"
  61. android:layout_height="wrap_content"
  62. android:background="@drawable/shape_button_circular"
  63. android:text="徐褚徐仲康" />
  64. <TextView
  65. android:layout_width="wrap_content"
  66. android:layout_height="wrap_content"
  67. android:background="@drawable/shape_button_circular"
  68. android:text="孙策孙伯符" />
  69. <TextView
  70. android:layout_width="wrap_content"
  71. android:layout_height="wrap_content"
  72. android:background="@drawable/shape_button_circular"
  73. android:text="太史慈" />
  74. <TextView
  75. android:layout_width="wrap_content"
  76. android:layout_height="wrap_content"
  77. android:background="@drawable/shape_button_circular"
  78. android:text="夏侯惇" />
  79. <TextView
  80. android:layout_width="wrap_content"
  81. android:layout_height="wrap_content"
  82. android:background="@drawable/shape_button_circular"
  83. android:text="夏侯渊" />
  84. <TextView
  85. android:layout_width="wrap_content"
  86. android:layout_height="wrap_content"
  87. android:background="@drawable/shape_button_circular"
  88. android:text="张辽" />
  89. <TextView
  90. android:layout_width="wrap_content"
  91. android:layout_height="wrap_content"
  92. android:background="@drawable/shape_button_circular"
  93. android:text="张郃" />
  94. <TextView
  95. android:layout_width="wrap_content"
  96. android:layout_height="wrap_content"
  97. android:background="@drawable/shape_button_circular"
  98. android:text="徐晃徐功明" />
  99. <TextView
  100. android:layout_width="wrap_content"
  101. android:layout_height="wrap_content"
  102. android:background="@drawable/shape_button_circular"
  103. android:text="庞德" />
  104. <TextView
  105. android:layout_width="wrap_content"
  106. android:layout_height="wrap_content"
  107. android:background="@drawable/shape_button_circular"
  108. android:text="甘宁甘兴霸" />
  109. <TextView
  110. android:layout_width="wrap_content"
  111. android:layout_height="wrap_content"
  112. android:background="@drawable/shape_button_circular"
  113. android:text="周泰" />
  114. <TextView
  115. android:layout_width="wrap_content"
  116. android:layout_height="wrap_content"
  117. android:background="@drawable/shape_button_circular"
  118. android:text="魏延" />
  119. <TextView
  120. android:layout_width="wrap_content"
  121. android:layout_height="wrap_content"
  122. android:background="@drawable/shape_button_circular"
  123. android:text="张绣" />
  124. <TextView
  125. android:layout_width="wrap_content"
  126. android:layout_height="wrap_content"
  127. android:background="@drawable/shape_button_circular"
  128. android:text="文丑" />
  129. <TextView
  130. android:layout_width="wrap_content"
  131. android:layout_height="wrap_content"
  132. android:background="@drawable/shape_button_circular"
  133. android:text="颜良" />
  134. <TextView
  135. android:layout_width="wrap_content"
  136. android:layout_height="wrap_content"
  137. android:background="@drawable/shape_button_circular"
  138. android:text="邓艾" />
  139. <TextView
  140. android:layout_width="wrap_content"
  141. android:layout_height="wrap_content"
  142. android:background="@drawable/shape_button_circular"
  143. android:text="姜维" />
  144. </com.zs.test.customview.ZSFlowLayout>
  145. </LinearLayout>
  146. </ScrollView>

也可以在代码中动态添加view(更接近实战,实战中数据多是后台请求而来)

  1. class FlowActivity : AppCompatActivity() {
  2. @BindView(id = R.id.activity_flow_flowlayout)
  3. var flowLayout : ZSFlowLayout ? = null;
  4. override fun onCreate(savedInstanceState: Bundle?) {
  5. super.onCreate(savedInstanceState)
  6. setContentView(R.layout.activity_customview_flow)
  7. BindViewInject.inject(this)
  8. for (i in 1 until 50) {
  9. val tv:TextView = TextView(this)
  10. tv.text = "TextView $i"
  11. flowLayout!!.addView(tv)
  12. }
  13. }
  14. }

其中BindViewInject是用反射+注解实现的一个小工具类

  1. object BindViewInject {
  2. /**
  3. * 注入
  4. *
  5. * @param activity
  6. */
  7. @JvmStatic
  8. fun inject(activity: Activity) {
  9. inject(activity, false)
  10. }
  11. fun inject(activity: Activity, isSetOnClickListener: Boolean) {
  12. //第一步 获取class对象
  13. val aClass: Class<out Activity> = activity.javaClass
  14. //第二步 获取类本身定义的所有成员变量
  15. val declaredFields = aClass.declaredFields
  16. //第三步 遍历找出有注解的属性
  17. for (i in declaredFields.indices) {
  18. val field = declaredFields[i]
  19. //判断是否用BindView进行注解
  20. if (field.isAnnotationPresent(BindView::class.java)) {
  21. //得到注解对象
  22. val bindView = field.getAnnotation(BindView::class.java)
  23. //得到注解对象上的id值 这个就是view的id
  24. val id = bindView.id
  25. if (id <= 0) {
  26. Toast.makeText(activity, "请设置正确的id", Toast.LENGTH_LONG).show()
  27. return
  28. }
  29. //建立映射关系,找出view
  30. val view = activity.findViewById<View>(id)
  31. //修改权限
  32. field.isAccessible = true
  33. //第四步 给属性赋值
  34. try {
  35. field[activity] = view
  36. } catch (e: IllegalAccessException) {
  37. e.printStackTrace()
  38. }
  39. //第五步 设置点击监听
  40. if (isSetOnClickListener) {
  41. //这里用反射实现 增加练习
  42. //第一步 获取这个属性的值
  43. val button = field.get(activity)
  44. //第二步 获取其class对象
  45. val javaClass = button.javaClass
  46. //第三步 获取其 setOnClickListener 方法
  47. val method =
  48. javaClass.getMethod("setOnClickListener", View.OnClickListener::class.java)
  49. //第四步 执行此方法
  50. method.invoke(button, activity)
  51. }
  52. }
  53. }
  54. }
  55. }
  1. @Target(AnnotationTarget.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. annotation class BindView( //value是默认的,如果只有一个参数,并且名称是value,外面传递时可以直接写值,否则就要通过键值对来传值(例如:value = 1)
  4. // int value() default 0;
  5. val id: Int = 0
  6. )

总结

到此这篇关于Android中FlowLayout组件实现瀑布流效果的文章就介绍到这了,更多相关Android FlowLayout瀑布流内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号