从0開始写MyScrollView

上篇文章对ScrollView的详细实现进行了分析。本文依据上篇分析的结果。自己动手写一个ScrollView。

step1 尾随手指滑动,非常easy。重写2个函数就好了

简单的滑动,仅仅要重写onTouchEvent就能够了。然后我们须要内部的LinearLayout高度能够超出MyScrollView,那就在measure过程中进行处理,重写measureChildWithMargins就能够了。


/**
* Created by fish on 16/8/2.
*/
public class MyScrollView extends FrameLayout { private boolean mIsBeingDragged = false;
/**
* Position of the last motion event.
*/
private int mLastMotionY;
private int mTouchSlop; public MyScrollView(Context context) {
this(context, null);
} public MyScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
} public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initScrollView();
} private void initScrollView() {
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
} //让内部的LinearLayout高度能够非常大非常大
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int delta = (int) (event.getY() - mLastMotionY);
if (mIsBeingDragged) {
scrollBy(0, -delta);
mLastMotionY= (int) event.getY();
} else if (Math.abs(delta) > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionY= (int) event.getY();
scrollBy(0, -delta);
}
break; case MotionEvent.ACTION_UP:
mIsBeingDragged = false;
break;
} return true;
}
}

step2 增加scrollbar

When you create a custom view you need to do the following to support

scrollbars:

- Enable the scrollbars

- Override the various compute*ScrollOffset, compute*ScrollRange(), etc. to

return sensible values

- Call awakenScrollbars() when you want to display the scrollbars (this is

called by the scroll methods in View as well)

http://markmail.org/thread/n7wv2rvgre3talba

要重写computeVerticalScrollOffset。computeVerticalScrollRange,初始化的时候调用setWillNotDraw(false);(为什么要setWillNotDraw(false)呢。由于默认ViewGroup是不绘制的,仅仅是个容器,可是这里要画滑块。所以得setWillNotDraw(false))

以上几点还不够。还得配置view的style属性。

从上篇文章我们知道ScrollView还配置了com.android.internal.R.attr.scrollViewStyle。 那我们怎样增加这个默认的style呢?我们知道这个style本质上是Widget.ScrollView,所以能够这样, style=”@android:style/Widget.ScrollView”非常关键,直接把style指定。

跟自己定义属性相关的知识能够參考http://blog.csdn.net/lmj623565791/article/details/45022631。写的非常好。

    <com.fish.myscrollviewpractise.MyScrollView
style="@android:style/Widget.ScrollView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"> <LinearLayout
android:id="@+id/linear1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"> </LinearLayout>
</com.fish.myscrollviewpractise.MyScrollView>

好了,此时scrollbar已经有了

话说回来。我们有必要搞清楚,为什么这样子就有scrollbar了

先看下scrollbar是什么时候调用的,调用图例如以下

//View#onDrawScrollBars
scrollBar.setParameters(computeVerticalScrollRange(),
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);

在view的onDrawScrollBars内部。须要setParameters。此时调用computeVerticalScrollRange和computeVerticalScrollOffset。这2个函数,我们进行重写。

    @Override
protected int computeVerticalScrollOffset() {
// LogUtil.fish("computeVerticalScrollOffset");
//这么写是考虑了OverScroller的情况
return Math.max(0, super.computeVerticalScrollOffset());
} @Override
protected int computeVerticalScrollRange() {
// LogUtil.fish("computeVerticalScrollRange");
final int count = getChildCount();
final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();
if (count == 0) {
return contentHeight;
} int scrollRange = getChildAt(0).getBottom();
final int scrollY = getScrollY();
final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
// if (scrollY < 0) {
// scrollRange -= scrollY;
// } else if (scrollY > overscrollBottom) {
// scrollRange += scrollY - overscrollBottom;
// } return overscrollBottom;
}

此时有一个问题不太理解,为什么滚动停止了。滚动栏就消失了?答案在下边,state会变为ScrollabilityCache.OFF,就不会仅仅滚动栏了。


protected final void onDrawScrollBars(Canvas canvas) {
// scrollbars are drawn only when the animation is running
final ScrollabilityCache cache = mScrollCache;
if (cache != null) { int state = cache.state; if (state == ScrollabilityCache.OFF) {
//滚好了就会走到这里。那就不调用 onDrawVerticalScrollBar,所以不绘制滚动栏
return;
}
。。。
scrollBar.setParameters(computeHorizontalScrollRange(),
![]() computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
。 。 。
onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom);
。。。

step3 滚完不要立马停下来,依据惯性再滚一会

速度达到一定程度。才会有惯性滚动,所以我们要检測速度,增加VelocityTracker。假设不熟悉VelocityTracker能够參考VelocityTracker

我们增加了

private VelocityTracker mVelocityTracker;

private Scroller mScroller;

在onTouchevent内有例如以下代码

     case MotionEvent.ACTION_UP:

                if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
mScroller.startScroll(getScrollX(), getScrollY(), 0, initialVelocity > 0 ? -300 : 300, 4000);
invalidate();
}
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
    private void endDrag() {
mIsBeingDragged = false;
recycleVelocityTracker();
}

step4 Scroller改为OverScroller

依据官方建议把Scroller改为OverScroller,增加fling代码。

看下边代码,把overY设置为height / 2。

overY代表能够超出边界多大距离,height / 2事实上这是比較大的一个值,滑的时候会导致超过边界较多距离。而原生是ScrollView不会超过边界非常多距离,这是为什么?

假设我们想要超过边界的距离小一点全然能够把这个值改小,比方改为100,这个地方写height / 2我也认为非常奇怪,暂且无论。

 mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height / 2);

step5 滚动的时候考虑边界,增加onScrollChanged

之前,我们直接用scrollTo,没有考虑边界的问题。

此时其有用overScrollBy比較合适。overScrollBy()会考虑边界以及over区域。

overScrollBy()是view的方法。会回调onOverScrolled()。所以我们还须要重写onOverScrolled().onOverScrolled(int scrollX, int scrollY,

boolean clampedX, boolean clampedY)这个函数是在overScrollBy内部调用的,overScrollBy会依据边界值以及over值计算出合适的scrollX和scrollY,而clampedX和clampedY代表着scrollX和scrollY的值是否被裁剪过(超出上下限就会被裁剪),假设被裁剪过overScrollBy的返回值就是true。否则就是false。

主要代码例如以下所看到的:

   @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) { int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY(); if (oldX != x || oldY != y) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
0, mOverflingDistance, false);
onScrollChanged(getScrollX(), getScrollY(), oldX, oldY); } postInvalidate();
}
} @Override
protected void onOverScrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
final int oldX = getScrollX();
final int oldY = getScrollY();
setScrollX(scrollX);
setScrollY(scrollY);
// invalidateParentIfNeeded();
//源代码里有这句,可是我认为不是必需写。 onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
if (clampedY) {
mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange());
}
} else {
super.scrollTo(scrollX, scrollY);
} awakenScrollBars();
}

主要解释3点,

第一。onOverScrolled()的2个分支是怎么回事?普通滑动调用的是下边super.scrollTo(scrollX, scrollY);fling走的是上边,假设超出边界须要用mScroller.springBack来复位。

第二,onOverScrolled里面为什么调用awakenScrollBars(),这句话的作用是要求绘制的时候加上scrollBar,曾经我们不写这句话是由于scrollTo()方法内部包括了这句话

第三,onOverScrolled里面有这句话onScrollChanged。事实上是不是必需的,由于在computeScroll是会调用的。所以反复了。可是呢,写这个也有一点优点,那就是我们监控onScrollChanged的时候,假设发现同样的值出现了2次,那我们就知道这是出于惯性滑动的状态(fling)

step6 move事件也用overScrollBy处理

这是为了解决一个问题,曾经拉到顶部了,还能够继续下拉

            case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
} final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true; //把deltaY弄小一点,这事实上无所谓的
if (deltaY > 0) {
deltaY -= mTouchSlop;
} else {
deltaY += mTouchSlop;
}
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
mLastMotionY = y; // final int oldY = getScrollY();
final int range = getScrollRange();
// final int overscrollMode = getOverScrollMode(); // Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollBy(0, deltaY, 0, getScrollY(), 0, range, 0, mOverscrollDistance, true)) {
//被裁剪了说明滑到头了。此时清除mVelocityTracker,是为了up的时候计算不出速度。速度为0,就没有fling了
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
} }

step7 边缘拉的时候增加晕影效果

ScrollView边缘拉的时候有晕影效果。这是怎么做到的呢?

EdgeEffect。增加此效果,主要四步

第一步,在View初始化的时候。会调用setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);

我们重写此函数,在内部构造mEdgeGlowTop和mEdgeGlowTop

  //在view的init里面被调用
@Override
public void setOverScrollMode(int mode) {
if (mode != OVER_SCROLL_NEVER) {
if (mEdgeGlowTop == null) {
Context context = getContext();
mEdgeGlowTop = new EdgeEffect(context);
mEdgeGlowBottom = new EdgeEffect(context);
}
} else {
mEdgeGlowTop = null;
mEdgeGlowBottom = null;
}
super.setOverScrollMode(mode);
}

第二步,在computeScroll内增加mEdgeGlowTop.onAbsorb。onAbsorb是初始化一堆參数为后面的draw做准备

    @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) { int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY(); if (oldX != x || oldY != y) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
0, mOverflingDistance, false);
onScrollChanged(getScrollX(), getScrollY(), oldX, oldY); if (canOverscroll) {
if (y < 0 && oldY >= 0) {
mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
} else if (y > range && oldY <= range) {
mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
}
} } postInvalidate();
}
}

第三步,重写onDraw()。增加绘制mEdgeGlowTop和mEdgeGlowBottom的代码。此处代码抄自ScrollView。

第四步,在endDrag的时候进行release。这是和onAbsorb相应的。清除各种数据

        if (mEdgeGlowTop != null) {
mEdgeGlowTop.onRelease();
mEdgeGlowBottom.onRelease();
}

第五步,在onTouchevent的move事件里,对下拉。上拉做响应,调用mEdgeGlowTop.onPull,呈现出拖拽效果

else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight(),
ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
1.f - ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}
if (mEdgeGlowTop != null
&& (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
postInvalidateOnAnimation();
}
}

step8 增加onInterceptTouchEvent

这部分代码不难理解,可是实际调用的机会比較少,主要实现2个功能。child处理了down,我能够抢个move(假设够大的话);配合onTouchevent实现fling时点击停止。

  @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/ /*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
} /*
* Don't try to intercept touch if we can't scroll anyway.
*/
if (getScrollY() == 0 && !canScrollVertically(1)) {
return false;
} switch (action & MotionEvent.ACTION_MASK) {
//down事件child处理的。我有权截获move事件
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/ /*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
} final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + activePointerId
+ " in onInterceptTouchEvent");
break;
} final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev); final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
} //配合完毕fling时。点击停止滚动
case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
} /*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0); initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished(); break;
} case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
break; } /*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged; }

step9 增加cancel事件处理。增加requestDisallowInterceptTouchEvent

cancel事件。就是收到前驱事件,后边的事件被parent抢走了,此时触发cancel,进行重置处理。

requestDisallowInterceptTouchEvent就是请求parent放过事件,都给我吧。

相关代码例如以下

    @Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
recycleVelocityTracker();
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
          //onTouchEvent
//假设cancel了就结束滚动
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
endDrag();
}

OK。此时大功告成,一个可用的ScrollView已经完毕了,功能有滚时显示滑块。普通滑动。惯性滑动,fling时点击停止,滚动能够超出边界并回弹,到达边界是有晕影效果等功能。

github地址

从0開始写MyScrollView的更多相关文章

  1. 【从0開始Tornado建站】0.9版本号python站点代码开源--持续更新中

            从5月份開始[从0開始Tornado建站]这个专栏,開始一点一点把这个分类兴趣站点弄起来,从无到有的过程也是令人兴奋的:-) 国庆的时候等待备案然后上线,如今站点域名为ustchack ...

  2. 从0開始学习 GitHub 系列之「07.GitHub 常见的几种操作」

    之前写了一个 GitHub 系列,反响非常不错,突然发现居然还落下点东西没写,前段时间 GitHub 也改版了,借此机会补充下. 我们都说开源社区最大的魅力是人人多能够參与进去,发挥众人的力量,让一个 ...

  3. 【从0開始Tornado建站】整体设计

    Tornado是一个非堵塞的webserver,也是python的web框架中很优秀的一款.网上关于django的tutorial许多并且具体,关于tornado的使用就很少了.我想以我从0開始的方式 ...

  4. [Golang] 从零開始写Socket Server(3): 对长、短连接的处理策略(模拟心跳)

    通过前两章,我们成功是写出了一套凑合能用的Server和Client,并在二者之间实现了通过协议交流.这么一来,一个简易的socket通讯框架已经初具雏形了,那么我们接下来做的.就是想办法让这个框架更 ...

  5. 从头開始写项目Makefile(十):make内嵌函数及make命令显示

    [版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com]     这一节我们讲一下make的函数,在之前的章节已经讲到了几 ...

  6. 从零開始写游戏引擎(一) - project创建以及文件夹设置还有版本号控制

    一句话提要 好的開始等于成功了一半. 创建文件夹结构 project文件夹下最好分为以下几个文件夹 Docs - 开发文档,设计文档 Assets - 角色,动作,模型和音效等 Source - 代码 ...

  7. 从头開始写项目Makefile(七):统一目标输出文件夹

    [版权声明:转载请保留出处:blog.csdn.net/gentleliu. Mail:shallnew at 163 dot com]     上一节我们把规则单独提取出来,方便了Makefile的 ...

  8. [Golang] 从零開始写Socket Server(4):将执行參数放入配置文件(XML/YAML)

    为了将我们写好的Server公布到server上.就要将我们的代码进行build打包.这样假设以后想要改动一些代码的话.须要又一次给代码进行编译打包并上传到server上.     显然,这么做过于繁 ...

  9. [Golang] 从零開始写Socket Server(2): 自己定义通讯协议

    在上一章我们做出来一个最基础的demo后,已经能够初步实现Server和Client之间的信息交流了~ 这一章我会介绍一下怎么在Server和Client之间实现一个简单的通讯协议.从而增强整个信息交 ...

随机推荐

  1. java过滤特殊字符的正则表达式

    // 过滤特殊字符 public staticString StringFilter(String str) throws PatternSyntaxException { // 只允许字母和数字 / ...

  2. JKS和PFX文件相互转换方法

    JKS(JavaKeysotre)格式和PFX(PKCS12)格式,是最常见的SSL证书格式文件,可以包含完整的证书密钥对,证书链和信任证书信息.PFX常用于Windows IIS服务器,JKS常用语 ...

  3. SpringBoot下的Job定时任务

    编写Job定时执行任务十分有用,能解决很多问题,这次实习的项目里做了一下系统定时更新三方系统订单状态的功能,这里用到了Spring的定时任务使用的非常方便,下面总结一下如何使用: 一,@schedul ...

  4. 豪斯医生第一季/全集House M.D 1迅雷下载

    豪斯医生 第一季 House M.D. Season 1 (2004)本季看点:态度无礼,表情凶恶,跛足拄着一根藤棍,永远是牛仔裤运动鞋的便装打扮而不是整洁的白大褂,普林斯顿大学附属医院的格雷戈·豪斯 ...

  5. windows核心编程-互斥器(Mutexes)

    线程同步的方式主要有:临界区.互斥区.事件.信号量四种方式. 前边讲过了临界区线程同步-----windows核心编程-关键段(临界区)线程同步,这章我来介绍一下互斥器(Mutexes)在线程同步中的 ...

  6. 浅谈提升C#正则表达式效率

     摘要:说到C#的Regex,谈到最多的应该就是RegexOptions.Compiled这个东西,传说中在匹配速度方面,RegexOptions.Compiled是可以提升匹配速度的,但在启动速度上 ...

  7. iOS开发-iOS8地理位置定位

    现在的App基本上都有定位功能,旅游网站根据定位推荐旅游景点,新闻App通过地理位置推荐当地新闻,社交类的App通过位置交友,iOS中实现以上功能需要一个核心的框架CoreLocation,框架提供了 ...

  8. Backbone.js 使用模板

    实际的应用中会使用到模板,Model 等,而模板又是进阶的基础.所以这里介绍在 View 中使用模板,以及如何向模板填充值,模板可以用字符串,或是用 <script type="tex ...

  9. [Math]理解卡尔曼滤波器 (Understanding Kalman Filter)

    1. 卡尔曼滤波器介绍 卡尔曼滤波器的介绍, 见 Wiki 这篇文章主要是翻译了 Understanding the Basis of the Kalman Filter Via a Simple a ...

  10. 学习世界模型,通向AI的下一步:Yann LeCun在IJCAI 2018上的演讲

    https://baijiahao.baidu.com/s?id=1606296521706399213&wfr=spider&for=pc 机器之心整理,机器之心编辑部. 人工智能顶 ...