1.Scroller是什么?

Scroller本身不会去移动View,它是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹的记录工具,最终还是通过View的scrollTo,scrollBy方法完成View的移动;

2.Scroller源码分析,如何实现滚动轨迹记录的呢?

再讲源码之前先了解两个重要的方法:

1)startScroll()

public void startScroll(int startX, int startY, int dx, int dy, int duration) {}

开始一个动画控制,由(startX,startY)在duration时间内前进(dx,dy)个单位,即达到偏移坐标为(startX+dx,startY+dy)

2)computeScrollOffset()

public boolean computeScrollOffset()

滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中。

上面两个方法的源码如下:

public class Scroller  {private int mStartX; //水平方向,滑动时的起点偏移坐标private int mStartY; //垂直方向,滑动时的起点偏移坐标private int mFinalX; //滑动完成后的偏移坐标,水平方向private int mFinalY; //滑动完成后的偏移坐标,垂直方向private int mCurrX;  //滑动过程中,根据消耗时间计算出的当前的滑动偏移距离,水平方向private int mCurrY;  //滑动过程中,根据消耗时间计算出的当前的滑动偏移距离,垂直方向private int mDuration; //本次滑动的动画时间private float mDeltaX; //滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向   private float mDeltaY; //滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向   public void startScroll(int startX, int startY, int dx, int dy) {startScroll(startX, startY, dx, dy, DEFAULT_DURATION);}/***  开始一个动画控制,由(startX,startY)在duration时间内前进(dx,dy)个单位,即达到偏移坐标为(startX+dx,startY+dy)*/public void startScroll(int startX, int startY, int dx, int dy, int duration) {mMode = SCROLL_MODE;mFinished = false;mDuration = duration;mStartTime = AnimationUtils.currentAnimationTimeMillis();mStartX = startX;mStartY = startY;mFinalX = startX + dx;    //确定本次滑动完成后的偏移坐标mFinalY = startY + dy;mDeltaX = dx;mDeltaY = dy;mDurationReciprocal = 1.0f / (float) mDuration;}/***    滑动过程中,根据当前已经消耗的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中*/public boolean computeScrollOffset() {if (mFinished) {    //已经完成了本次动画控制,直接返回为falsereturn false;}int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);if (timePassed < mDuration) {switch (mMode) {case SCROLL_MODE:final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);mCurrX = mStartX + Math.round(x * mDeltaX);//计算出当前滑动的偏移位置,x轴mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前滑动的偏移位置,y轴break;...}}else {mCurrX = mFinalX;mCurrY = mFinalY;mFinished = true;}return true;}
}

Scroller类中最重要的两个方法就是startScroll()和computeScrollOffset(),当时Scroller类只是一个滑动计算辅助类,它的startScroll()和computeScrollOffset()方法中也只是对一些轨迹参数进行设置和计算,真正需要进行滑动还的通过View的scrollTo(),scrollBy()方法。为此,View中提供了computeScroll方法来控制这个滑动流程。computeScroll()方法会在绘制子视图的时候进行调用。源码如下:

//由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
public void computeScroll(){} //空方法,自定义滑动功能的ViewGroup必须实现的方法体

因此Scroller类的基本使用流程可以总结如下:

(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;

(2)在调用 startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;

(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。 如下的一个简单代码示例:

    @Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int)event.getX();switch (event.getAction()){case MotionEvent.ACTION_DOWN:mLastX = x;break;case MotionEvent.ACTION_MOVE:int dx = mLastX - x;int oldScrollX = getScrollX();  //原来的偏移量int preScrollX = oldScrollX + dx; //本次滑动后的形成的偏移量if(preScrollX<0){preScrollX = 0;dx = preScrollX - oldScrollX;}if(preScrollX>(getChildCount()-1)*getWidth()){preScrollX = (getChildCount()-1)*getWidth();dx = preScrollX - oldScrollX;}mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0,0);invalidate();
//                scrollTo(preScrollX, getScrollY());Log.d("MyViewPager", "oldScrollX:"+oldScrollX+" dx:"+dx);mLastX = x;break;}return true;}@Overridepublic void computeScroll() {super.computeScroll();if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(), mScroller.getCurrY());Log.d("MyViewPager", "mScroller.getCurrX():"+mScroller.getCurrX());invalidate();}}

3.Scroller负责记录View的轨迹信息,View如何根据轨迹信息完成滚动操作、坐标体系规则是什么?

View通过scrollTo(),scrollBy()完成滚动操作;

scrollTo,scrollBy方法是View中的,因此任何View都可以通过这两个方法进行移动。首先要明白的是,scrollTo、scrollBy滑动的是View中的内容(而且还是整体滑动),而不是View本身。我们滑动控件如ScrollView可以限定宽,高大小,以及在布局中的位置,但是滑动控件中的内容(或者里面的childView)可以无限长、宽的,我们调用View的scrollTo,scrollBy方法,相当于是移动滑动控件中的画布canvas,然后进行重绘,屏幕上也可以显示相应的内容。如下:

1).getScrollX()、getScrollY()

在学习scrollTo(),scrollBy()之前,先来了解一下getScrollX(),getScrollY()方法。

getScrollX(),getScrollY()得到的是偏移量,是相对自己初始位置的滑动偏移距离,只有当scroll事件发生时,这个两个方法才有值,否则getScrollX(),getScrollY()都是初始时的值为0,而不管你这个滑动控件在哪里。所谓自己初始位置是指,控件在刚开始显示时,没有滑动前的位置。以getScrollX()为例,其源码如下:

    public final int getScrollX() {return mScrollX;}

可以看到getScrollX()直接返回的是mScrollX,代表水平方向的偏移量,getScrollY也类似。偏移量mScroll的正负代表着滑动控件中的内容相对于 初始位置在水平方向上偏移的情况,mScrollX为正代表着当前内容相对于初始位置向左偏移了mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。

这里的坐标系和我们平常的认知正好相反。为了以后更方便的处理滑动相应坐标和偏移,在处理偏移,滑动相关的功能时,我们可以把坐标反过来看,如下图:

因为滑动控件中的内容是整体进行滑动的,同时也是相对于自己显示时的初始位置的偏移,对于View中的内容在偏移时的参考坐标原点(注意是内容视图的坐标原点,不是图中说的滑动控件的原点),可以选择初始位置的某一个地方,因为滑动时整体行为,在进行滑动时候从这个选择的原点出进行分析即可。

2).scrollTo(),scrollBy()

scrollTo(int x,int y)移动的是View中的内容,而滑动控件中的内容都是整体移动的,scrollTo(int x,int y)中的参数表示View中的内容相对于内容初始位置移动x和y的距离,即将内容移动到距离内容初始位置x和y的位置。正如前面所说,在处理偏移,滑动问题时坐标系和平常认知的坐标系是相反的,以一个例子说明scrollTo():

说明:图中黄色矩形区域表示的是一个可滑动的View控件,绿色虚线矩形为滑动控件中的滑动内容。注意这里的坐标是相反的。

a.调用scrollTo(100,0)表示将View中的内容移动到距离内容初始显示位置的x=100,y=0的地方,效果如下图:

b.调用scrollTo(0,100)效果如下图:

c.调用scrollTo(100,100)效果如下图:

d.调用scrollTo(-100,0)效果如下图:

通过上面几个图,可以清楚看到scrollTo的作用和滑动坐标系的关系。在实际使用中,我们一般是在onTouchEvent()方法中处理滑动事件,在MotionEvent.ACTION_MOVE时调用scrollTo(int x,int y)进行滑动,在调用scrollTo(int x,int y)前,我们先要计算出两个参数值,即水平和垂直方向需要滑动的距离。如下:

 @Overridepublic boolean onTouchEvent(MotionEvent event) {int y = (int)event.getY();int action = event.getAction();switch (action){case MotionEvent.ACTION_DOWN:mLastY = y;break;case MotionEvent.ACTION_MOVE:int dy = mLastY - y; //本次手势滑动了多大距离int oldScrollY = getScrollY(); //先计算之前已经偏离多少距离int scrollY = oldScrollY + dy; //本次需要偏移的距离=以前已经便宜的距离+本次手势滑动了多大距离if(scrollY <= 0){scrollY = 0;}if(scrollY > getHeight() - mScreenHeight){scrollY = getHeight() - mScreenHeight;}scrollTo(getScrollX(), scrollY);mLastY = y;break;}return true;}

上面再计算参数时,分了三步。第一是,通过int dy = mLastY-y;得到本次手势在屏幕上滑动了多少距离,这里特别注意这个相减的顺序,因为这里的坐标与平常是相反的,因此,手势滑动距离是按下时的坐标mLastY-当前的坐标y;第二是,通过oldScrollY=getScrollY();获得滑动内容之前已经距离初始位置偏移了多少;第三个是,计算本次需要偏移的参数int scrollY = oldScrollY+dy;后面通过两个if条件进行了边界处理,然后调用scrollTo进行滑动。调用完scrollTo后,新的偏移量又重新产生了。从scrollTo源码中可以看到:

public void scrollTo(int x, int y) {  if (mScrollX != x || mScrollY != y) {  int oldX = mScrollX;  int oldY = mScrollY;  mScrollX = x;//赋值新的x偏移量  mScrollY = y;//赋值新的y偏移量  invalidateParentCaches();  onScrollChanged(mScrollX, mScrollY, oldX, oldY);  if (!awakenScrollBars()) {  postInvalidateOnAnimation();  }  }  }  

scrollTo是相对于初始位置来进行滑动的,scrollBy(int x,int y)则是相对于上一次移动的距离来进行的本次移动;scrollBy其实还是依赖于scrollTo的,如下源码:

public void scrollBy(int x, int y) {  scrollTo(mScrollX + x, mScrollY + y);  }  

可以看到,使用scrollBy其实就是省略了我们在计算scrollTo参数时的第三部而已,因为scrollBy内部已经自己帮我加上了第三部的计算。因此scrollBy的作用就是相当于在上一次的偏移情况下进行本次偏移。

一个完整的水平方向滑动的例子:

public  class MyViewPager extends ViewGroup {//手指在屏幕水平方向X轴坐标private int mLastX;//Scroller滚动辅助类private Scroller mScroller;public MyViewPager(Context context) {this(context, null);}public MyViewPager(Context context, AttributeSet attrs) {this(context, attrs,0);}public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context);}private void init(Context context) {mScroller = new Scroller(context);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int count = getChildCount();for (int i = 0; i < count; i++) {View child = getChildAt(i);child.measure(widthMeasureSpec, heightMeasureSpec);}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b);int count = getChildCount();for (int i = 0; i < count; i++) {View child = getChildAt(i);//子视图水平方向布局显示child.layout(getWidth()*i,t,getWidth()*(i+1),b);}}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int)event.getX();switch (event.getAction()){case MotionEvent.ACTION_DOWN:mLastX = x;break;case MotionEvent.ACTION_MOVE://X轴偏移量int dx = mLastX - x;int oldScrollX = getScrollX();  //原来的偏移量int preScrollX = oldScrollX + dx; //本次滑动后的形成的偏移量(最终偏移位置)//越界检查-左侧,越界需要重新计算需要偏移的量dxif(preScrollX<0){preScrollX = 0;dx = preScrollX - oldScrollX;}//越界检查-右侧,越界需要重新计算需要偏移的量dxif(preScrollX>(getChildCount()-1)*getWidth()){preScrollX = (getChildCount()-1)*getWidth();dx = preScrollX - oldScrollX;}//调用mScroller的startScroll方法计算滚动轨迹mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0,0);//必须要调用invalidate方法提示View内容显示位置变化invalidate();//去掉如下注释就不需要写如下三个方法(mScroller.startScroll()+invalidate()+computeScroll())
//              scrollTo(preScrollX, getScrollY());Log.d("MyViewPager", "oldScrollX:"+oldScrollX+" dx:"+dx);mLastX = x;break;}return true;}@Overridepublic void computeScroll() {super.computeScroll();//true:表示滚动未结束,false:滚动完成if(mScroller.computeScrollOffset()){//滚动内容到当前位置X,Y坐标scrollTo(mScroller.getCurrX(), mScroller.getCurrY());Log.d("MyViewPager", "mScroller.getCurrX():"+mScroller.getCurrX());invalidate();}}
}

布局文件:

<net.sourceforge.simcpux.MyViewPagerandroid:layout_width="match_parent"android:layout_height="300dp"><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/scroll_1" /><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/scroll_2" /><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/ic_launcher" /><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/ic_launcher" /></net.sourceforge.simcpux.MyViewPager>

效果如图:

4.Scroller具体使用示例-自定义ViewGroup实现分页显示?

自定义ViewPager:

public class MyViewPager3 extends ViewGroup {private int mLastX;private Scroller mScroller;private VelocityTracker mVelocityTracker;private int mTouchSlop;private int mMaxVelocity;/***  当前显示的第几个屏幕*/private int mCurrentPage = 0;public MyViewPager3(Context context) {this(context, null);}public MyViewPager3(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MyViewPager3(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public MyViewPager3(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context);}private void init(Context context) {mScroller = new Scroller(context);ViewConfiguration config = ViewConfiguration.get(context);mTouchSlop = config.getScaledPagingTouchSlop();mMaxVelocity = config.getScaledMinimumFlingVelocity();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int count = getChildCount();for (int i = 0; i < count; i++) {View child = getChildAt(i);child.measure(widthMeasureSpec,heightMeasureSpec);}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int count = getChildCount();Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b);for (int i = 0; i < count; i++) {View child = getChildAt(i);child.layout(i*getWidth(),t,(i+1)*getWidth(),b);}}@Overridepublic boolean onTouchEvent(MotionEvent event) {initVelocityTrackerIfNotExists();mVelocityTracker.addMovement(event);int x = (int)event.getX();switch (event.getAction()){case MotionEvent.ACTION_DOWN:if(!mScroller.isFinished()){mScroller.abortAnimation();}mLastX = (int)event.getX();break;case MotionEvent.ACTION_MOVE:int dx = mLastX - x;int oldScrollX = getScrollX();int preScrollX = oldScrollX + dx;if(preScrollX>(getChildCount()-1)*getWidth()){preScrollX = (getChildCount()-1)*getWidth();dx = preScrollX - oldScrollX;}if(preScrollX < 0){preScrollX = 0;dx = preScrollX - oldScrollX;}mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(),dx,0);//注意,使用startScroll后面一定要进行invalidate刷新界面,// 触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,// 只是一个辅助类,并不会触发界面的绘制invalidate();
//                scrollBy(dx,0);mLastX = x;break;case MotionEvent.ACTION_UP:final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000);int initVelocity = (int)velocityTracker.getXVelocity();//如果是快速的向右滑,则需要显示上一个屏幕if(initVelocity>mMaxVelocity && mCurrentPage>0){Log.d("TAG","----------------快速的向右滑--------------------");scrollToPage(mCurrentPage-1);}else if(initVelocity<-mMaxVelocity && mCurrentPage<getChildCount()-1){Log.d("TAG","----------------快速的向左滑--------------------");scrollToPage(mCurrentPage+1);}else{//不是快速滑动的情况,此时需要计算是滑动到Log.d("TAG","----------------慢慢的滑动--------------------");slowScrollTopPage();}recycleVelocityTracker();break;}return true;}/*** 缓慢滑动抬起手指的情形,需要判断是停留在本Page还是往前、往后滑动*/private void slowScrollTopPage(){//当前的偏移位置int scrollX = getScrollX();int scrollY = getScrollY();//判断是停留在本Page还是往前一个page滑动或者往后一个page滑动int whichPage = (getScrollX()+getWidth()/2)/getWidth();scrollToPage(whichPage);}/*** 滑动到指定屏幕* @param page*/private void scrollToPage(int page) {mCurrentPage = page;if(mCurrentPage > getChildCount() - 1){mCurrentPage = getChildCount() - 1;}//计算滑动到指定Page还需要滑动的距离int dx = mCurrentPage*getWidth() - getScrollX();//动画时间设置为Math.abs(dx) * 2 msmScroller.startScroll(getScrollX(),0,dx,0);//记住,使用Scroller类需要手动invalidateinvalidate();}@Overridepublic void computeScroll() {Log.d("TAG", "---------computeScrollcomputeScrollcomputeScroll--------------");super.computeScroll();if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(),mScroller.getCurrY());invalidate();}}private void recycleVelocityTracker(){if(mVelocityTracker != null){mVelocityTracker.recycle();mVelocityTracker = null;}}private void initVelocityTrackerIfNotExists(){if(mVelocityTracker == null){mVelocityTracker = VelocityTracker.obtain();}}}

布局文件:

<net.sourceforge.simcpux.MyViewPager3android:layout_width="match_parent"android:layout_height="300dp"><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/scroll_1" /><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/scroll_2" /><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/ic_launcher" /><ImageViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/ic_launcher" /></net.sourceforge.simcpux.MyViewPager3>

5.NestedScrollView实现原理分析?

待续...

6.简单总结

1)Scroller类能够帮助我们实现高级滑动功能,如手指抬起的惯性滑动功能。使用流程为,首先通过Scroller的startScroll()+invalidate()触发View的computeScroll,在computeScroll()中让Scroller类去计算最新的坐标信息,

拿到最新的坐标偏移信息后还是要调用View的scrollTo来实现滑动。可以看到,使用Scroller的整个流程比较简单,关键的是控制滑动的一些逻辑计算,比如上面例子中的计算什么时候该往哪一页滑动...

(2)Android后面推出了OverScroller类,OverScroller在整体功能上和Scroller类似,使用也相同。OverScroller类可以完全代替Scroller,相比Scroller,OverScroller主要是增加了对滑动到边界的一些控制,如增加一些回弹效果等,功能更加强大。

参考:

Android Scroller详解

高级UI<第四十四篇>:Android Scroller详解

图解Android View的scrollTo(),scrollBy(),getScrollX(), getScrollY()

Android之Scroller详解讲解-真正了解滚动处理相关推荐

  1. Android Studio 插件开发详解三:翻译插件实战

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78113868 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  2. Android 动画框架详解,第 1 部分

    2019独角兽企业重金招聘Python工程师标准>>> Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果,本文将向读者阐述 Android 的动画框 ...

  3. Android中AMS工作原理,Android AMS启动详解

    启动 在Android系统启动流程中中我们提到过,AMS是在system_service中启动的, //frameworks/base/services/java/corri/android/serv ...

  4. android Json解析详解(详细代码)

    android Json解析详解(详细代码)   JSON的定义: 一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性.业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当 ...

  5. Android编译过程详解(三)

    Android编译过程详解(一):http://www.cnblogs.com/mr-raptor/archive/2012/06/07/2540359.html Android编译过程详解(二):h ...

  6. Carson带你学Android:图文详解RxJava背压策略

    前言 Rxjava,由于其基于事件流的链式调用.逻辑简洁 & 使用简单的特点,深受各大 Android开发者的欢迎. 本文主要讲解的是RxJava中的 背压控制策略,希望你们会喜欢. Cars ...

  7. 视频教程-Android Studio 开发详解-Android

    Android Studio 开发详解 1999年开始从事开发工作,具备十余年的开发.管理和培训经验. 在无线通信.Android.iOS.HTML5.游戏开发.JavaME.JavaEE.Linux ...

  8. android组件模板,提高效率必备神器 ---- Android Studio模板详解

    原标题:提高效率必备神器 ---- Android Studio模板详解 Android Studio模板大家应该很熟悉,你新建一个project或者module的时候,AS会帮你提供几个选项供你选择 ...

  9. Android 动画框架详解

    Android 动画框架详解 基本原理 朱 韦伟, 软件工程师, IBM 李 浩, 软件工程师, 爱格码 简介: Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果.A ...

最新文章

  1. 职场观察:高薪需要什么?
  2. int的长度_Java中String长度有限制吗?身边的同事每一个人知道!
  3. redis学习(七)jedis客户端
  4. 20211104 为什么矩阵的迹等于特征值之和,为什么矩阵的行列式等于特征值之积
  5. 前后端分离时代,Java 程序员的变与不变!
  6. .Net微服务实战之技术选型篇
  7. 2016年全国计算机技术与软件,2016年的上半年的全国计算机技术与软件专业技术资格(水平)考.doc...
  8. 【python】Get与Post的区别?(面试官最想听到的答案)
  9. html5之api,HTML5之API(示例代码)
  10. mysql jpa uuid_在spring data jpa使用UUID
  11. 10-08 长假结束
  12. 什么是分布式系统,如何学习分布式系统
  13. 供应XBF-01型多功存储介质粉碎机
  14. ubunt18 mysql_Ubuntu18.04下安装MySQL教程
  15. WBE15_HttpServletRequest
  16. linux leach仿真数据传输图性能,基于OMNeT-+-+的Leach协议的仿真研究.pdf
  17. java 对Excel 操作 例子(上)
  18. 【matlab图像处理】图像处理工具箱(2)
  19. 莫名其妙CSDN不让我发帖子了。看来10年的老水手我该离开CSDN了。
  20. 电子计算机X线体层摄影,X线计算机体层摄影.pdf

热门文章

  1. 2022年Android官方模拟器安装Xposed教程+测试工具PatDroid安装教程
  2. 记录-吴军《硅谷来信》有感
  3. 解决win10系统点击飞行模式后找不到WiFi连接问题
  4. TCP/IP协议头部结构
  5. 锐捷设备AC虚拟化(VAC)
  6. 1秒钟组装发动机,我震惊了
  7. 第11届蓝桥杯 Java C 组真题 A:指数计算
  8. 移动硬盘删除的文件如何恢复
  9. 苹果计算机开机进入安全模式,iphone进入安全模式及退出安全模式的方法
  10. Hanoi Tower Troubles Again!