打造炫酷通用的ViewPager指示器-Adapter模式适配所有._第1页
打造炫酷通用的ViewPager指示器-Adapter模式适配所有._第2页
打造炫酷通用的ViewPager指示器-Adapter模式适配所有._第3页
打造炫酷通用的ViewPager指示器-Adapter模式适配所有._第4页
打造炫酷通用的ViewPager指示器-Adapter模式适配所有._第5页
已阅读5页,还剩19页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、打造炫酷通用的ViewPager指示器Adapter模式适配所有sm視冏 国片 锻子itw用Adapter适配器模式适配所有的效果,堪称终结者效果实现2.1整合上一个实例:我还是还是拿上一个实例来做演示吧。这里我贴几种常见的效果,首先声明An droid自带的有这个控件叫 TabLayout,大家可以自己用用试试看好用不?我也用过但是不做任何评 价,自己造的轮子还是想怎么用就怎么用。播1双12主建场芙妆我的方案i殳计师有案拆改Item布局不一样,还有上面是图片下面是文字选中的还有一些奇葩的效果如每个头部 效果各不相同等等,我们都要去适配。2.2 实现思路:我在老早的时候用过 ViewPageI

2、ndicator ,还没毕业出来工作的时候, 好不好用我也 不做评价, 就是那个时候搞了一晚上没搞出来第二天一看原来是 activity 的 Theme 主题没有 配置,大家手上肯定也有类似的效果也都可以用,只是以个人的理解来自己造一个轮子。2.2.1 控件肯定是继承 ScrollView 因为可以左右滑动,如果再去自定义 ViewGroup 肯定不划算。2.2.2 怎样才能适合所有的效果,难道我们把所有可能出现的效果都写一遍吗?这 的确不太可能,所以肯定采用 Adapter 适配器模式。2.2.3 我们先动起来从简单的入手,先做到动态的添加不同的布局条目再说吧。2.3 自定义 TrackIn

3、dicatorView 动态添加布局:这里为了适配所有效果,所以决定采用适配器Adapter 设计模式,上面也提到过。至于什么是适配器模式大家需要看一下这个1. 模式介绍1.1 模式的定义:适配器模式把一个类的接口变换成客户端所期待的另一种接口, 从而使原本因接口 不匹配而无法在一起工作的两个类能够在一起工作。1.2 模式的使用场景:用电源接口做例子,笔记本电脑的电源一般都是接受 5V 的电压,但是我们生活中 的电线电压一般都是 220V 的输出。这个时候就出现了不匹配的状况,在软件开发中我们称 之为接口不兼容, 此时就需要适配器来进行一个接口转换。 在软件开发中有一句话正好体现 了这点:任何

4、问题都可以加一个中间层来解决。这个层我们可以理解为这里的 Adapter 层, 通过这层来进行一个接口转换就达到了兼容的目的。2.模式的简单实现2 .1简单实现的介绍:在上述电源接口这个示例中, 5V 电压就是 Target 接口, 220v 电压就是 Adaptee 类, 而将电压从 220V 转换到 5V 就是 Adapter 。2 .2 类适配器模式 :* Target 角色*/public interface FiveV olt public int getV olt5();/* Adaptee 角色 , 需要被转换的对象*/public class Volt220 public in

5、t getV olt220() return 220;/ adapter 角色public class ClassAdapter extends Volt220 implements FiveV olt Overridepublic int getV olt5() return 5;Target 角色给出了需要的目标接口,而 Adaptee 类则是需要被转换的对象。 Adapter 则 是将 Volt220 转换成 Target 的接口。对应的是 Target 的目标是要获取 5V 的输出电压,而 Adaptee 即正常输出电压是 220V ,此时我们就需要电源适配器类将220V 的电压转换为

6、 5V电压,解决接口不兼容的问题。public class Test public static void main(String args) ClassAdapter adapter = new ClassAdapter();System.out.println( 输出电压 : + adapter.getVolt5();2 .3.Android源码中的模式实现与类的适配器模式一样,对象的适配器模式把被适配的类的API 转换成为目标类的API ,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用代理关系连接到 Adaptee类。从图2可以看出,Adapt

7、ee类(Volt220 )并没有getVolt5()方法,而客户端则 期待这个方法。为使客户端能够使用Adaptee类,需要提供一个包装类 Adapter。这个包装类包装了一个 Adaptee的实例,从而此包装类能够把 Adaptee的API与Target类的API衔接 起来。 Adapter 与 Adaptee 是委派关系,这决定了适配器模式是对象的。/* Target 角色*/public interface FiveV olt public int getV olt5();/* Adaptee 角色 , 需要被转换的对象 */public class Volt220 public int

8、 getV olt220() return 220;/ 对象适配器模式public class ObjectAdapter implements FiveV olt Volt220 mV olt220;public ObjectAdapter(V olt220 adaptee) mVolt220 = adaptee;public int getV olt220() return mV olt220.getV olt220();Overridepublic int getV olt5() return 5;2 4类适配器和对象适配器的权衡* 类适配器使用对象继承的方式,是静态的定义方式;而对象适

9、配器使用对象 组合的方式,是动态组合的方式。 对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和 Adaptee的子类一起工作,因为继承是静态的关系,当适配器继承了 Adaptee 后,就不可能再去处理 Adaptee 的子类了。对于对象适配器,一个适配器可以把多种不同的源适配到同一个目标。 换言之, 同一个适配器可以把源类和它的子类都适配到目标接口。 因为对象适配器采用的是 对象组合的关系,只要对象类型正确,是不是子类都无所谓。* 对于类适配器, 适配器可以重定义 Adaptee 的部分行为, 相当于子类覆盖父类 的部分实现方法。对于对象适配器,要重定义 Adaptee 的行

10、为比较困难,这种情况下,需要 定义 Adaptee 的子类来实现重定义,然后让适配器组合子类。虽然重定义 Adaptee 的行为比 较困难,但是想要增加一些新的行为则方便的很, 而且新增加的行为可同时适用于所有的源。* 对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。对于对象适配器,需要额外的引用来间接得到Adaptee。建议尽量使用对象适配器的实现方式,多用合成 /聚合、少用继承。当然,具体问 题具体分析,根据需要来选用实现方式,最适合的才是最好的。3 .Android ListView 中的 Adapter 模式在开发过程中 ,ListView 的 Adapt

11、er 是我们最为常见的类型之一。一般的用法大致如下 :/ 适配器public class MyAdapter extends BaseAdapterprivate LayoutInflater mInflater;List mDatas ;public MyAdapter(Context context, List datas)this.mInflater = LayoutInflater.from(context);mDatas = datas ;Overridepublic int getCount() return mDatas.size();Overridepublic String

12、getItem(int pos) return mDatas.get(pos);Overridepublic long getItemId(int pos) return pos;/ 解析、设置、缓存 convertView 以及相关内容Overridepublic View getView(int position, View convertView, ViewGroup parent) ViewHolder holder = null;/ Item View 的复用if (convertView = null) holder = new ViewHolder();convertView =

13、 mInflater.inflate(R.layout.my_listview_item, null); / 获取 title holder.title = (TextView)convertView.findViewById(R.id.title);convertView.setTag(holder); else holder = (ViewHolder)convertView.getTag();holder.title.setText(mDatas.get(position);return convertView;这看起来似乎还挺麻烦的,看到这里我们不禁要问, ListView 为什么要使

14、用 Adapter 模 式呢?我们知道,作为最重要的 View ,ListView 需要能够显示各式各样的视图,每个人需要 的显示效果各不相同, 显示的数据类型、 数量等也千变万化。 那么如何隔离这种变化尤为重 要。Android 的做法是增加一个 Adapter 层来应对变化,将 ListView 需要的接口抽象到 Adapter 对象中,这样只要用户实现了 Adapter 的接口, ListView 就可以按照用户设定的显 示效果、数量、数据来显示特定的 Item View 。通过代理数据集来告知 ListView 数据的个数 ( getCount 函数 )以及每个数据的类型 ( getI

15、tem 函数 ) ,最重要的是要解决 Item View 的输出。 Item View 千变万化,但终究它都是 View 类型, Adapter 统一将 Item View 输出为 View ( getView 函数 ),这样就很好的应对了 Item View 的可变性。那么 ListView 是如何通过 Adapter 模式 ( 不止 Adapter 模式 ) 来运作的呢 ?我们一起 来看一看。ListView 继承自 AbsListView , Adapter 定义在 AbsListView 中,我们看一看这个类。public abstract class AbsListView exte

16、nds AdapterView implements TextWatcher,ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback ListAdapter mAdapter ;/ 关联到 Window 时调用的函数Overrideprotected void onAttachedToWindow() super.onAttachedT

17、oWindow();/ 代码省略/ 给适配器注册一个观察者 ,该模式下一篇介绍。if (mAdapter != null & mDataSetObserver = null) mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver);/ Data may have changed while we were detached. Refresh. mDataChanged = true;mOldItemCount = mItemCount/ 获取 Item

18、 的数量 ,调用的是 mAdapter 的 getCount 方法 mItemCount = mAdapter.getCount();mIsAttached = true;/* 子类需要覆写 layoutChildren() 函数来布局 child view, 也就是 Item View */Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) super.onLayout(changed, l, t, r, b);mInLayout = true;if (changed) int childC

19、ount = getChildCount();for (int i = 0; i childCount; i+) getChildAt(i).forceLayout();mRecycler.markChildrenDirty();if (mFastScroller != null & mItemCount != mOldItemCount) mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);/ 布局 Child ViewlayoutChildren();mInLayout = false;mOverscrollMax = (

20、b - t) / OVERSCROLL_LIMIT_DIVISOR;/ 获取一个 Item ViewView obtainView(int position, boolean isScrap) isScrap0 = false;View scrapView;/ 从缓存的 Item View 中获取 ,ListView 的复用机制就在这里 scrapView = mRecycler.getScrapView(position);View child;if (scrapView != null) / 代码省略child = mAdapter.getView(position, scrapView,

21、 this);/ 代码省略 else child = mAdapter.getView(position, null, this);/ 代码省略return child;AbsListView 定义了集合视图的框架, 比如 Adapter 模式的应用、 复用 Item View 的逻辑、 布局 Item View 的逻辑等。子类只需要覆写特定的方法即可实现集合视图的功能,例如 ListView 。ListView 中的相关方法。Overrideprotected void layoutChildren() / 代码省略try super.layoutChildren();invalidate(

22、);/ 代码省略/ 根据布局模式来布局 Item View switch (mLayoutMode) case LAYOUT_SET_SELECTION:if (newSel != null) selfillFromSelection(newSel.getTop(),childrenTop,childrenBottom); else sel = fillFromMiddle(childrenTop, childrenBottom);break;case LAYOUT_SYNC:sel = fillSpecific(mSyncPosition, mSpecificTop);break;case

23、LAYOUT_FORCE_BOTTOM:sel = fillUp(mItemCount - 1, childrenBottom);adjustViewsUpOrDown();break;case LAYOUT_FORCE_TOP:mFirstPosition = 0;sel = fillFromTop(childrenTop);adjustViewsUpOrDown();break;case LAYOUT_SPECIFIC:sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);break;case LAYOUT_MOVE_S

24、ELECTION:sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);break;default:/ 代码省略break;/ 从上到下填充 Item View 只是其中一种填充方式 private View fillDown(int pos, int nextTop) View selectedView = null;int end = (mBottom - mTop);if (mGroupFlags & CLIP_TO_PADDING_MASK) =CLIP_TO_PADDING_MASK) end

25、-= mListPadding.bottom;while (nextTop end & pos mItemCount) / is this the selected item?boolean selected = pos = mSelectedPosition;View child = makeAndAddView(pos, nextTop, true, mListPadding.left,selected);nextTop = child.getBottom() + mDividerHeight; if (selected) selectedView = child;pos+;return

26、selectedView;/ 添加 Item Viewprivate View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) View child;/ 代码省略/ Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap);/ This needs to be positioned and measured

27、setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap0);return child;ListView 覆写了 AbsListView 中的 layoutChilden 函数,在该函数中根据布局模式来布局 Item View。Item View的个数、样式都通过 Adapter对应的方法来获取,获取个数、Item View之后,将这些 Item View 布局到 ListView 对应的坐标上,再加上 Item View 的复用机制,整 个 ListView 就基本运转起来了。当然这里的 Adapter 并不是

28、经典的适配器模式,但是却是对象适配器模式的优秀示例, 也很好的体现了面向对象的一些基本原则。这里的 Target 角色和 Adapter 角色融合在一起, Adapter中的方法就是目标方法; 而Adaptee角色就是ListView的数据集与Item View , Adapter 代理数据集,从而获取到数据集的个数、元素。通过增加 Adapter 一层来将 Item View 的操作抽象起来, ListView 等集合视图通过 Adapter 对象获得 Item 的个数、数据元素、 Item View 等,从而达到适配各种数据、各种 Item 视图 的效果。因为 Item View 和数据类

29、型千变万化, Android 的架构师们将这些变化的部分交给 用户来处理,通过 getCount、 getItem、 getView 等几个方法抽象出来,也就是将 Item View 的构造过程交给用户来处理, 灵活地运用了适配器模式, 达到了无限适配、 拥抱变化的目的。4.杂谈优点与缺点优点更好的复用性系统需要使用现有的类, 而此类的接口不符合系统的需要。 那么通过适配器模式就 可以让这些功能得到更好的复用。更好的扩展性在实现适配器功能的时候, 可以调用自己开发的功能, 从而自然地扩展系统的功能。 缺点过多的使用适配器, 会让系统非常零乱, 不易整体进行把握。 比如,明明看到调用的是 A 接

30、口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一 场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。比如,明明看到调用的是 A 接口这是理论篇,但是仔细看过我博客的哥们应该知道我 其实 Adapter 设计模式理论与实践相结合写过很多效果和框架了。 这里不做过多的讲解, 写 着写着看着看着就会了就理解了。2.3.1 我们再也不能直接传字符串数组或是传对象数组过去让自定义 View 去处理 了,所以我们先确定一个自定义的Adapter 类,getCount()和 getView(int position,ViewGroupparent) 先用这

31、两个方法吧后面想到了再说。* Created by Darren on 2016/12/7.* Email: 240336124* Description: 指示器的适配器 */public abstract class IndicatorBaseAdapter/ 获取总的条数public abstract int getCount();/ 根据当前的位置获取 Viewpublic abstract View getView(int position,ViewGroup parent);2.3.2 然 后 我 们 来 实 现 指 示 器 的 自 定 义 View , TrackIndicato

32、rView 继 承 自HorizontalScrollView 。然后我们利用传递过来的 Adapter 再去动态的添加, 我这里就直接上 代码吧/* Created by Darren on 2016/12/13.* Email: 240336124* Description: ViewPager 指示器*/public class TrackIndicatorView extends HorizontalScrollView / 自定义适配器private IndicatorBaseAdapter mAdapter;/ Item 的容器因为 ScrollView 只允许加入一个孩子priv

33、ate LinearLayout mIndicatorContainer;public TestIndicator(Context context) this(context, null);public TestIndicator(Context context, AttributeSet attrs) this(context, attrs, 0);public TestIndicator(Context context, AttributeSet attrs, int defStyleAttr) super(context, attrs, defStyleAttr);/ 初始化 Indic

34、ator 容器用来存放 itemmIndicatorContainer = new LinearLayout(context); addView(mIndicatorContainer);public void setAdapter(IndicatorBaseAdapter adapter) if (adapter = null) throw new NullPointerException(Adapter cannot be null!);this.mAdapter = adapter;/ 获取 Item 个数int count = mAdapter.getCount();/ 动态添加到布局

35、容器for (int i = 0; i count; i+) View indicatorView = mAdapter.getView(i, mIndicatorContainer); mIndicatorContainer.addView(indicatorView);效果可想而知, 可以写一个 Activity 测试一下, 目前可以动态的添加多个不同样式的布 局,如果超出一个屏幕可以左右滑动,我这里就不做演示,待会一起吧。2.3.3 动态的制定指示器 Item 的宽度:目前我们虽然能够动态的去添加各种布局,但是 Item 的宽度是任意的,我们需要 在布局文件中指定一屏显示多少个,如果没有

36、指定那么就获取 Item 中最宽的一个,如果不 够一屏显示就默认显示一屏。 我们需要使用自定义属性, 这里就不做过多的讲, 实在不行大 家就自己去看看有关自定义属性的博客或是直接 google 搜索一下。/ 获取一屏显示多少个 Item, 默认是 0private int mTabVisibleNums = 0;/ 每个 Item 的宽度private int mItemWidth = 0;public TrackIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) super(context, attrs,

37、defStyleAttr);/ 之前代码省略 ./ 获取自定义属性值 一屏显示多少个TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TrackIndicatorView);mTabVisibleNums = array.getInt(R.styleable.TrackIndicatorView_tabVisibleNums, mTabVisibleNums);array.recycle();Overrideprotected void onLayout(boolean changed, int l, in

38、t t, int r, int b) super.onLayout(changed, l, t, r, b);if (changed) / 指定 Item 的宽度mItemWidth = getItemWidth();int itemCounts = mAdapter.getCount();for (int i = 0; i + mItemWidth);* 获取每一个条目的宽度*/public int getItemWidth() int itemWidth = 0;/ 获取当前控件的宽度int width = getWidth();if (mTabVisibleNums != 0) / 在布

39、局文件中指定一屏幕显示多少个 itemWidth = width / mTabVisibleNums; return itemWidth;/ 如果没有指定获取最宽的一个作为 ItemWidthint maxItemWidth = 0;int mItemCounts = mAdapter.getCount();/ 总的宽度int allWidth = 0;for (int i = 0; i mItemCounts; i+) View itemView = mIndicatorContainer.getChildAt(i);int childWidth = itemView.getMeasured

40、Width(); maxItemWidth = Math.max(maxItemWidth, childWidth); allWidth += childWidth;itemWidth = maxItemWidth;/ 如果不足一个屏那么宽度就为 width/mItemCountsif (allWidth +positio n+positio nO ffset - +positio nO ffset);/在不断滚动的时候让头部的当前Item 直保持在最中心in dicatorScrollTo(positi on ,positi onO ffset);*不断的滚动头部*/private void

41、 indicatorScrollTo(int position, float positionOffset) /当前的偏移量 int curre ntOffset = (int) (positi on + positi onO ffset) * mltemWidth);/原始的左边的偏移量 in t origin LeftOffset = (getWidth()-mltemWidth)/2;/当前应该滚动的位置 int scrollToOffset = curre ntOffset - origi nLeftOffset; / 调用 ScrollView 的 scrollTo 方法scroll

42、To(scrollToOffset,0);目前我们滚动鑒醮的时蟆,当前指示器条目会一直保持在摄中心,口】皿潤 代码我就没贴出来了,遠个待会可旦下载我的濾玛看看.我们看看效杲2.4.2.点击Item之后ViewPager能够切换到对应的页面public void setAdapter(IndicatorBaseAdapter adapter) if (er = null) throw new NullPointerException(Adapter cannot be null!); this.mAdapter = adapter;/ 获取 Item 个数int count = mAdapter

43、.getCount();/ 动态添加到布局容器for (int i = 0; i count; i+) View indicatorView = mAdapter.getView(i, mIndicatorContainer); mIndicatorContainer.addView(indicatorView); switchIndicatorClick(indicatorView,i);* Indicator 条目点击对应切换 ViewPager */private void switchIndicatorClick(View indicatorView, final int positi

44、on) indicatorView.setOnClickListener(new OnClickListener() Overridepublic void onClick(View v) if(mViewPager != null)/ 对应切换 ViewPager mViewPager.setCurrentItem(position);/ IndicatorItem 对应滚动到最中心 indicatorSmoothScrollTo(position););int scrollToOffset = currentOffset - originLeftOffset;/ smoothScrollT

45、osmoothScrollTo(scrollToOffset,0);我们运行起来之后会发现一个问题, 我们点击会切换对应的 ViewPager 但是这个时候还 是会调用 onPageScrolled() 方法,这个就比较 dan 疼,所以我们必须解决,如果是点击我就 不让其执行 onPageScrolled() 里面的代码。2.4.3. 需要页面切换之后需要回调,让用户切换当前选中的状态,需要在 Adapter 中增加方法在 Adapter 中增加两个回调方法,一个是高亮当前选中方法 highLightIndicator(View view) ,恢复默认方法 restoreIndicator(

46、View view) ,这两个方法可以不用写成抽象的,为了 方便我们干脆使用泛型* Created by Darren on 2016/12/7.* Email: 240336124* Description: 指示器的适配器*/public abstract class IndicatorBaseAdapter / 获取总的条数 public abstract int getCount();/ 根据当前的位置获取 Viewpublic abstract Q getView(int position, ViewGroup parent);/ 高亮当前位置public void highLigh

47、tIndicator(Q indicatorView)/ 重置当前位置 public void restoreIndicator(Q indicatorView)TrackIndicatorViewOverride public void onPageSelected(int position) / 重置上一个位置的状态 View lastView = mIndicatorContainer.getChildAt(mCurrentPosition);mAdapter.restore In dicator(lastView); /高亮当前位置的状态mCurre ntPositi on = pos

48、iti on; highLight In dicator(mCurre ntPositi on);*高亮当前位置*/private void highLight In dicator(i nt positi on) View curre ntView = mln dicatorC ontain er.getChildAt(positi on); mAdapter.highLightI ndicator(curre ntView);谡子当前这里下吧。一步两步一步两步总算是快到头了,接下来我们只需要加入指示器就可以了,面涉及到属性动画,如果不是很了解那就去看一下我的视频或者去google官网看2.

49、4.4. 有些效果需要加入指示器,但并不是每种效果都需要* Created by Darren on 2016/12/7.* Email: 240336124* Descriptio n:指示器的容器包括下标*/public class IndicatorContainer extends RelativeLayout private LinearLayout mIndicatorContainer;private Context mContext;/ 底部跟踪的 Viewprivate View mBottomTrackView;private String TAG = IndicatorC

50、ontainer;/ 距离左边的初始距离private int mInitLeftMargin = 0;private RelativeLayout.LayoutParams mBottomTrackParams;private int mTabWidth;public IndicatorContainer(Context context) this(context, null);public IndicatorContainer(Context context, AttributeSet attrs) this(context, attrs, 0);public IndicatorConta

51、iner(Context context, AttributeSet attrs, int defStyleAttr) super(context, attrs, defStyleAttr);this.mContext = context;Overridepublic void addView(View child) if (mIndicatorContainer = null) / 初始化容器mIndicatorContainer = new LinearLayout(mContext);RelativeLayout.LayoutParams params = new LayoutParams( ViewGroup.LayoutParams.MATCH_

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论