首页 文章 精选 留言 我的

精选列表

搜索[鸿蒙课程],共8135篇文章
优秀的个人博客,低调大师

7.kotlin安卓实践课程-用kotlin写第一个activity对应M层

简介 主要会通过安卓实战来讲解kotlin语法和实际应用,本教程设及知识点包括框架模式mvp+mvvm, Databinding(数据绑定框架),Dagger2(依赖注入框架),DeepLink(页面路由框架),Rxjava,RxAndroid(异步操作框架),Retrofit,Okhtttp等,不过本教程重点在kotlin所以这些框架需要了解可自行百度。 针对人群:Android中级以上,kotlin入门 上一篇文章讲了RandomPresenter的代码构成 在上篇RandomPresenter代码中有这么一段 mModel.getRandom(type) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ res -> if (!res.error) { mView.onRandom(res.results[0]) } }, {})) mModel.getRandom(type)这句话就是在mModel里去请求网络,具体实现我们看看 /** * Created by bigman on 18-05-17. */ class RandomModel @Inject constructor(private val service:RandomService) :RandomContract.Model{ override fun getRandom(type:String): Observable<JsonResult<List<FuckGoods>>> { return service.getRandom(type) } } 这个RandomModel主要是提供了一个注入的构造函数,在这里它的构造函数里面传入了一个RandomService接口,这个接口的实现如下 /** * Created by liuguirui on 2018/5/17. */ interface RandomService { /** * 手气不错 */ @GET("random/data/{type}/1") fun getRandom(@Path("type") type:String): Observable<JsonResult<List<FuckGoods>>> } 熟悉 Retrofit+Rxjava的同学应该很清楚这个接口类就是用来声明服务器接口和参数声明的,它和我们正常java写的retrofit接口没太大的区别,这里只有一个getRandom方法 然后让我们再回到RandomModel class RandomModel @Inject constructor(private val service:RandomService) :RandomContract.Model 这里的constructor我们前面我们见过但是我没说,大概英文水平8都是八级以上肯定猜到是构造函数吧。 语法点:构造函数 1.分类 kotlin中构造函数一种主构造函数和次构造函数,而java是没有主次之分但是java也是支持多个构造函数的 2.主构造函数 主构造函数是写在类名后面的,比如我们这里的class RandomModel @Inject constructor(private val service:RandomService),这就是主构造函数的一般形式 主构造函数只能有一个,次构造函数可有可无 主构造函数不能包含任何的代码,但是初始化的代码可以放到init函数里面,这个我们前面说过 当主构造函数有注解或者可见性修饰符,需加 constructor 关键字,我们这里的代码就包含了注解 3.次构造函数 咱们这里没用到次构造函数没关系,kotlin的次构造函数和java的多个构造函数有点不同,kotlin的次构造函数必须直接或间接的委托给主构造函数 看以下代码 class Coder (name:String){ //直接委托 constructor(name: String,age:Int):this(name) { } //间接委托 constructor(name: String, age: Int,score:Int) : this(name, age) { } } 这里有几个点: class Coder (name:String)这里其实就是主构造函数 次构造函数必须用constructor关键字,而且必须用:冒号直接或间接委托给主构造函数 直接委托就是this关键字去调用的,比如这里的constructor(name: String,age:Int):this(name)就是通过this(name)直接调用了主构造函数 间接委托,比如这里的constructor(name: String, age: Int,score:Int) : this(name, age)是先用this(name, age)调用了第一个次构造函数,然后通过这个次构造函数间接的调用了主构造函数 然后我们回头看看RandomPresenter的构造函数,其实和这里的 RandomModel一样的主构造函数,看看它的代码 class RandomPresenter @Inject constructor(private val mModel: RandomModel, private val mView: RandomContract.View) : RandomContract.Presenter, BasePresenter() 其实这种形式在我们这个项目里非常常见大家以后见多了也就习惯了,我们顺便看完这个构造函数里的RandomContract的代码 /** * Created by bigman on 18-05-18. */ interface RandomContract{ interface View{ fun onRandom(goods:FuckGoods) } interface Model{ fun getRandom(type: String): Observable<JsonResult<List<FuckGoods>>> } interface Presenter{ fun getRandom(type: String) } } 熟悉google官方的mvp框架的同学应该知道这个接口文件的意义,这就是一个简单的协议接口,同时提供了mvp三个核心层次不同的接口定义,其实就是面向接口编程的一种标准形式 欢迎一起交流,有问题加群交流 kotlin实战交流群群二维码.png

优秀的个人博客,低调大师

6.kotlin安卓实践课程-用kotlin写第一个activity对应P层

简介 主要会通过安卓实战来讲解kotlin语法和实际应用,本教程设及知识点包括框架模式mvp+mvvm, Databinding(数据绑定框架),Dagger2(依赖注入框架),DeepLink(页面路由框架),Rxjava,RxAndroid(异步操作框架),Retrofit,Okhtttp等,不过本教程重点在kotlin所以这些框架需要了解可自行百度。 针对人群:Android中级以上,kotlin入门 上一篇文章讲了MainActivity的代码构成 MainActivity当中有一段代码 floatingButton.setOnClickListener { mPresenter.getRandom("Android") } 它就是通过P层去请求网络,具体的定义我们先看看RandomPresenter的构成 /** * Created by bigman on 18-05-18. */ class RandomPresenter @Inject constructor(private val mModel: RandomModel, private val mView: RandomContract.View) : RandomContract.Presenter, BasePresenter() { override fun getRandom(type: String) { addSubscription( mModel.getRandom(type) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ res -> if (!res.error) { mView.onRandom(res.results[0]) } }, {})) } } 首先看看这个addSubscription是个什么方法 /** * Created by bigman on 18-05-18. */ open class BasePresenter { var compositeSubscription = CompositeSubscription() protected fun addSubscription(subscription: Subscription) { compositeSubscription.add(subscription) } fun unSubscribe() { if(compositeSubscription.hasSubscriptions()){ compositeSubscription.unsubscribe() } } } 每个Presenter都要继承于BasePresenter,然后由BasePresenter里面的CompositeSubscription对象统一管理订阅者的订阅和解绑(rxjava2实现就是观察者模式,不了解rxjava2的可以去学习一下rxjava2) 语法点:继承 open class BasePresenter 由于kotlin中所有类和方法默认都是final的,不能直接继承或重写,需要继承的类或类中要重写的方法都应当在定义时添加open关键字 然后又回到RandomPresenter mModel.getRandom(type) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ res -> if (!res.error) { mView.onRandom(res.results[0]) } }, {}) mModel接下来几节会说到,我们主要看看这个链式调用,这是一个基本的rx异步操作,通过mModel做网络请求,然后在安卓主线程观察一旦有结果返回就通过subscribe订阅结果处理数据,并且通过mView这个引用对象去回调通知界面做显示等UI操作 欢迎一起交流,有问题加群交流 kotlin实战交流群群二维码.png

优秀的个人博客,低调大师

人工智能之神经网络特训班课程过半,这些内容关键点你不能错过

2016 年,谷歌 AlphaGo 下围棋战胜了人类世界冠军李世石;美国白宫发布了人工智能白皮书;微软研发的 AI 语音识别首次超过了人类...人工智能一跃成为产业发展的主要方向、科技进步的关键源动力。 相信很多人都注意到了这一趋势,但现实是:仍有许多朋友对 AI 一知半解,如雾里看花。究其原因,或许可以归结为以下几点:找不到系统的学习资料,缺少经验丰富的“引路人”,以及没有一个合适的学习、交流平台。 为此,雷锋网(公众号:雷锋网)联合国内顶级 AI 培训平台“1024MOOC学院”,邀请到清华大学计算机系的博士生导师邓志东教授作为授课嘉宾,举办“人工智能之神经网络特训班”。邓教授从事包括深度神经网络在内的人工神经网络研究 25 年,在清华授课 20 余年,研究无人驾驶技术 8 年,具有非常丰富的研发和教学经验。 上周末(3 月 4 日

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:布局性能优化之按需加载View

原文地址:http://android.xsoftlab.net/training/improving-layouts/loading-ondemand.html 有时应用程序中会有一些很少用到的复杂布局。在需要它们的时候再加载可以降低内存的消耗,同时也可以加快界面的渲染速度。 定义ViewStub ViewStub是一个轻量级的View,它没有高宽,也不会绘制任何东西。所以它的加载与卸载的成本很低。每个ViewStub都可以使用android:layout属性指定要加载的布局。 下面这个ViewStub用于一个半透明的ProgressBar的加载。它只有在新工作开始时才会显示。 <ViewStub android:id="@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" /> 加载ViewStub 当需要加载由ViewStub所指定的布局时,可以使用setVisibility(View.VISIBLE)方法或者inflate()方法,两者效果相同。 ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate(); Note: inflate()方法会在加载完毕的时候返回一个View。所以不需要使用findViewById()来查找这个布局的Root View。 一旦ViewStub所托管的View被加载,那么ViewStub将不再是View层级的一部分。它会被所加载的布局替换,并且会将该布局的ID更改为ViewStub的android:inflatedId属性所指定的ID。 Note: ViewStub的缺点是:它当前并不支持要加载布局的root View为< merge/>标签。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:后台加载数据之使用CursorLoader进行查询

原文地址:http://android.xsoftlab.net/training/load-data-background/index.html 引言 在ContentProvider中查询数据是需要花点时间的。如果你直接在Activity进行查询,那么这可能会导致UI线程阻塞,并会引起”Application Not Responding”异常。就算不会发生这些事情,那么用户也能感觉到卡顿,这会非常恼人的。为了避免这样的问题,应该将查询的工作放在单独的线程中执行,然后等待它执行完毕后将结果显示出来。 你可以使用一个异步查询对象在后台查询,然后等查询结束之后再与Activity建立连接。这个对象就是我们要说的CursorLoader。CursorLoader除了可以进行基本查询之外,还可以在数据发生变化后自动的重新进行查询。 这节课主要会学习如何使用CursorLoader在后台进行查询。 使用CursorLoader进行查询 CursorLoader对象在后台运行着一个异步查询,当查询结束之后会将结果返回到Activity或FragmentActivity。这使得查询在进行的过程中Activity或FragmentActivity还可以继续与用户交互。 定义使用CursorLoader的Activity 如果要在Activity中使用CursorLoader,需要用到LoaderCallbacks接口。CursorLoader会调用该接口中的方法,从而使得与Activity产生交互。这节课与下节课都会详细描述该接口中的回调。 举个例子,下面的代码演示了如何定义一个使用了CursorLoader的FragmentActivity。通过继承FragmentActivity,你可以获得CursorLoader对Fragment的支持: public class PhotoThumbnailFragment extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { ... } 初始化查询 使用LoaderManager.initLoader()可以初始化查询。它其实初始化了后台查询框架。可以将初始化这部分工作放在用户输入了需要查询的数据之后,或者如果不需要用户输入数据,那么也可以将这部分工作放在onCreate()或onCreateView()中执行: // Identifies a particular Loader being used in this component private static final int URL_LOADER = 0; ... /* When the system is ready for the Fragment to appear, this displays * the Fragment's View */ public View onCreateView( LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { ... /* * Initializes the CursorLoader. The URL_LOADER value is eventually passed * to onCreateLoader(). */ getLoaderManager().initLoader(URL_LOADER, null, this); ... } Note: getLoaderManager()方法只对Fragment类可用。如果需要在FragmentActivity中获得LoaderManager,调用getSupportLoaderManager()方法即可。 开始查询 后台查询框架的初始化一旦完成,紧接着你所实现的onCreateLoader()就会被调用。如果要启动查询,需要在该方法内返回一个CursorLoader对象。你可以实例化一个空的CursorLoader,然后再使用它的方法定义查询,或者你也可以在实例化CursorLoader的时候定义查询。 /* * Callback that's invoked when the system has initialized the Loader and * is ready to start the query. This usually happens when initLoader() is * called. The loaderID argument contains the ID value passed to the * initLoader() call. */ @Override public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle) { /* * Takes action based on the ID of the Loader that's being created */ switch (loaderID) { case URL_LOADER: // Returns a new CursorLoader return new CursorLoader( getActivity(), // Parent activity context mDataUrl, // Table to query mProjection, // Projection to return null, // No selection clause null, // No selection arguments null // Default sort order ); default: // An invalid id was passed in return null; } } 一旦后台查询框架获得了该对象,那么它会马上在后台开始查询。当查询结果完成,后台查询框架会调用onLoadFinished(),该方法的具体内容会在下节课说明。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:键盘输入处理之处理键盘按键

原文地址:http://android.xsoftlab.net/training/keyboard-input/commands.html 当用户将焦点给到可编辑文本的View时,例如EditText这种,并且该设备还拥有实体键盘,那么所有的输入都会被系统处理。然而,如果你希望可以拦截或者直接处理键盘的输入事件的话,你可以通过实现回调方法KeyEvent.Callback接口来做到。比如onKeyDown()和onKeyMultiple()。 Activity与View都实现了KeyEvent.Callback接口,所以一般情况下应该重写这两个类的回调方法。 Note: 当通过KeyEvent类或其它相关API处理键盘的输入事件时,应当认为这些键盘事件都来自于实体键盘。绝不要仰仗接收软键盘的按键事件。 处理单个按键事件 如果要处理独立的按键事件,需要恰当的使用onKeyDown()方法或者onKeyUp()方法。通常情况下,如果要确保只有一个按键被按下时,应当只使用onKeyUp()方法。如果用户按下并没有放开某个按钮的话,那么onKeyDown()将会被调用多次。 举个例子,下面的实现通过响应某些按键来控制游戏: @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_D: moveShip(MOVE_LEFT); return true; case KeyEvent.KEYCODE_F: moveShip(MOVE_RIGHT); return true; case KeyEvent.KEYCODE_J: fireMachineGun(); return true; case KeyEvent.KEYCODE_K: fireMissile(); return true; default: return super.onKeyUp(keyCode, event); } } 处理组合按键 为了响应组合按键事件,比如某些按键需要与Shift或者Control组合使用,你可以查询通过回调方法传回的KeyEvent对象。一些方法还为组合按键的提供了查询信息的功能,比如getModifiers()和getMetaState()。。不管如何,最简单的方案就是通过isShiftPressed()或者isCtrlPressed()检查你所关心的组合按键是否被按下了。 举个例子,下面是onKeyUp()方法的改良版本,增添了一些专门对于Shift键的额外处理: @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { ... case KeyEvent.KEYCODE_J: if (event.isShiftPressed()) { fireLaser(); } else { fireMachineGun(); } return true; case KeyEvent.KEYCODE_K: if (event.isShiftPressed()) { fireSeekingMissle(); } else { fireMissile(); } return true; default: return super.onKeyUp(keyCode, event); } }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:手势处理之滚动动画及Scroller

原文地址:http://android.xsoftlab.net/training/gestures/scroll.html 在Android中,滑动经常由ScrollView类来实现。任何超出容器边界的布局都应该将自己内嵌在ScrollView中,以便提供可滚动的视图效果。自定义滚动只有在特定的场景下才会被用到。这节课将会描述这样一种场景:使用scroller显示一种可滚动的效果。 你可以使用Scroller或者OverScroller来收集一些滑动动画所需要的数据。这两个类很相似,但是OverScroller包含了一些用于指示已经到达布局边界的方法。示例InteractiveChart在这里使用了类EdgeEffect(实际上是类EdgeEffectCompat),在用户到达布局边界时会显示一种”glow“的效果。 Note: 我们推荐使用OverScroller。这个类提供了良好的向后兼容性。还应该注意,通常只有在实现滑动自己内部的时候,才需要使用到scroller类。如果你将布局嵌入到ScrollView或HorizontalScrollView中的话,那么它们会将滚动的相关事宜做好。 Scroller用于在时间轴上做动画滚动效果,它使用了标准的滚动物理学(摩擦力、速度等等)。Scroller本身不会绘制任何东西。Scroller会随着时间的变化追踪移动的偏移量,但是它们不会自动的将这些值应用在你的View上。你需要自己获取这些值并使用,这样才会使滑动的效果更流畅。 了解滑动术语 “Scrolling”这个词在Android中可被翻译为各种不同的意思,这取决于具体的上下文。 Scrolling是一种viewport(viewport的意思是,你所看到的内容的窗口)移动的通用处理过程。当scrolling处于x轴及y轴上时,这被称为“平移(panning)”。示例程序提供了相关的类:InteractiveChart,演示了滑动的两种不同类型,dragging(拖动)及flinging(滑动): Dragging 该滚动类型在这种情况下发生:当用户的手指在屏幕上来回滑动时。简单的Dragging由GestureDetector.OnGestureListener接口的onScroll()方法实现。 Flinging 该滚动类型在这种情况下发生:当用户快速在屏幕上滑动并离开屏幕时。在用户离开了屏幕之后,常理上应该保持滚动状态,并随之慢慢减速,直到viewport停止移动。Flinging由GestureDetector.OnGestureListener接口的onFling()方法及scroller对象所实现。 将scroller对象与滑动手势结合使用是一种共通的方法。你可以重写onTouchEvent()方法直接处理触摸事件,并降低滑动的速度来响应相应的触摸事件。 实现基础滚动 这部分章节将会描述如何使用scroller。下面的代码段摘自示例应用InteractiveChart。它在这里使用了一个GestureDetector对象,重写了GestureDetector.SimpleOnGestureListener的onFling()方法。它使用了OverScroller来追踪滑动中的手势。如果用户在滑动之后到达了内容的边缘,那么APP会显示一个”glow”的效果。 Note: 示例APP InteractiveChart 展示了一个图表,这个图标可以缩放、平移、滚动等等。在下面的代码段中,mContentRect代表了矩形的坐标点,而绘制图表的View则居于其中。在给定的时间内,一个总表的子集将会被绘制到这块区域内。mCurrentViewport代表了屏幕中当前可视的部分图表。因为像素的偏移量通常被当做整型,所以mContentRect的类型是Rect。因为图形的数据范围是小数类型,所以mCurrentViewport的类型是RectF。 代码段的第一部分展示了onFling()方法的实现: // The current viewport. This rectangle represents the currently visible // chart domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private RectF mCurrentViewport = new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); // The current destination rectangle (in pixel coordinates) into which the // chart data should be drawn. private Rect mContentRect; private OverScroller mScroller; private RectF mScrollerStartViewport; ... private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { // Initiates the decay phase of any active edge effects. releaseEdgeEffects(); mScrollerStartViewport.set(mCurrentViewport); // Aborts any active scroll animations and invalidates. mScroller.forceFinished(true); ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); return true; } ... @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { fling((int) -velocityX, (int) -velocityY); return true; } }; private void fling(int velocityX, int velocityY) { // Initiates the decay phase of any active edge effects. releaseEdgeEffects(); // Flings use math in pixels (as opposed to math based on the viewport). Point surfaceSize = computeScrollSurfaceSize(); mScrollerStartViewport.set(mCurrentViewport); int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - AXIS_X_MIN) / ( AXIS_X_MAX - AXIS_X_MIN)); int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - mScrollerStartViewport.bottom) / ( AXIS_Y_MAX - AXIS_Y_MIN)); // Before flinging, aborts the current animation. mScroller.forceFinished(true); // Begins the animation mScroller.fling( // Current scroll position startX, startY, velocityX, velocityY, /* * Minimum and maximum scroll positions. The minimum scroll * position is generally zero and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset should be 800 pixels. */ 0, surfaceSize.x - mContentRect.width(), 0, surfaceSize.y - mContentRect.height(), // The edges of the content. This comes into play when using // the EdgeEffect class to draw "glow" overlays. mContentRect.width() / 2, mContentRect.height() / 2); // Invalidates to trigger computeScroll() ViewCompat.postInvalidateOnAnimation(this); } 当onFling()方法调用postInvalidateOnAnimation()方法时,它会调用computeScroll()来更新x及y的值。 大多数View会将scroller对象的x及y的属性值直接设置给方法scrollTo()。而下面的computeScroll()则采用了不同的方法:它调用computeScrollOffset()来获取x及y的当前坐标。当显示的区域到达边界时,这里就会展示一个”glow”的效果,代码会设置一个越过边缘的效果,并会调用postInvalidateOnAnimation()来使View重新绘制。 // Edge effect / overscroll tracking objects. private EdgeEffectCompat mEdgeEffectTop; private EdgeEffectCompat mEdgeEffectBottom; private EdgeEffectCompat mEdgeEffectLeft; private EdgeEffectCompat mEdgeEffectRight; private boolean mEdgeEffectTopActive; private boolean mEdgeEffectBottomActive; private boolean mEdgeEffectLeftActive; private boolean mEdgeEffectRightActive; @Override public void computeScroll() { super.computeScroll(); boolean needsInvalidate = false; // The scroller isn't finished, meaning a fling or programmatic pan // operation is currently active. if (mScroller.computeScrollOffset()) { Point surfaceSize = computeScrollSurfaceSize(); int currX = mScroller.getCurrX(); int currY = mScroller.getCurrY(); boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN || mCurrentViewport.right < AXIS_X_MAX); boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN || mCurrentViewport.bottom < AXIS_Y_MAX); /* * If you are zoomed in and currX or currY is * outside of bounds and you're not already * showing overscroll, then render the overscroll * glow edge effect. */ if (canScrollX && currX < 0 && mEdgeEffectLeft.isFinished() && !mEdgeEffectLeftActive) { mEdgeEffectLeft.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectLeftActive = true; needsInvalidate = true; } else if (canScrollX && currX > (surfaceSize.x - mContentRect.width()) && mEdgeEffectRight.isFinished() && !mEdgeEffectRightActive) { mEdgeEffectRight.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectRightActive = true; needsInvalidate = true; } if (canScrollY && currY < 0 && mEdgeEffectTop.isFinished() && !mEdgeEffectTopActive) { mEdgeEffectTop.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectTopActive = true; needsInvalidate = true; } else if (canScrollY && currY > (surfaceSize.y - mContentRect.height()) && mEdgeEffectBottom.isFinished() && !mEdgeEffectBottomActive) { mEdgeEffectBottom.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectBottomActive = true; needsInvalidate = true; } ... } 下面是执行实际放大的代码: // Custom object that is functionally similar to Scroller Zoomer mZoomer; private PointF mZoomFocalPoint = new PointF(); ... // If a zoom is in progress (either programmatically or via double // touch), performs the zoom. if (mZoomer.computeZoom()) { float newWidth = (1f - mZoomer.getCurrZoom()) * mScrollerStartViewport.width(); float newHeight = (1f - mZoomer.getCurrZoom()) * mScrollerStartViewport.height(); float pointWithinViewportX = (mZoomFocalPoint.x - mScrollerStartViewport.left) / mScrollerStartViewport.width(); float pointWithinViewportY = (mZoomFocalPoint.y - mScrollerStartViewport.top) / mScrollerStartViewport.height(); mCurrentViewport.set( mZoomFocalPoint.x - newWidth * pointWithinViewportX, mZoomFocalPoint.y - newHeight * pointWithinViewportY, mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX), mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY)); constrainViewport(); needsInvalidate = true; } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); } 下面是computeScrollSurfaceSize()方法的内容。它计算了当前可滑动的界面的尺寸,以像素为单位。举个例子,如果整个图表区域是可见的,那么它的值就等于mContentRect。如果图标被放大了200%,那么返回的值就是水平及垂直方向值的两倍。 private Point computeScrollSurfaceSize() { return new Point( (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) / mCurrentViewport.width()), (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) / mCurrentViewport.height())); }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:手势处理之记录手指移动的轨迹

原文地址:http://android.xsoftlab.net/training/gestures/movement.html 这节课将会学习如何在触摸事件中记录手指移动的轨迹。 当手指触摸的位置、压力或者尺寸发生变化时,ACTION_MOVE事件就会被触发。与Detecting Common Gestures中描述的一样,所有的事件都被记录在一个MotionEvent对象中。 因为基于手指的触摸并不是很精确的交互方式,所以检测触摸事件的行为需要更多的轨迹点。为了帮助APP区分基于轨迹的手势(比如滑动等移动的手势)与非轨迹手势(比如单点等不移动的手势),Android提出了一个名为”touch slop”的概念。Touch slop指的是用户按下的以像素为单位的距离。 这里有若干项不同的追踪手势轨迹的方法,具体使用哪个方法取决于应用程序的需求: 指针的起始位置与结束位置。 指针位移的方向,由X,Y的坐标判断。 历史记录,你可以通过getHistorySize()获得手势的历史尺寸。然后可以通过getHistorical(Value)方法获得这些历史事件的位置,尺寸,事件以及压力。当渲染手指的轨迹时,比如在屏幕上用手指画线条等,历史记录这时就会派上用场。 指针在屏幕上滑动的速度。 轨迹的速度 在记录手势的特性或者在检查何种手势事件发生时,除了要依靠手指移动的距离、方向这两个要素之外。还需要另外一个非常重要的因素就是速度。为了使速度计算更加容易,Android为此提供了VelocityTracker类以及VelocityTrackerCompat类。VelocityTracker用于辅助记录触摸事件的速度。这对于判断哪个速度是手势的标准部分,比如飞速滑动。 下面的例子用于演示在VelocityTracker API中方法的目的: public class MainActivity extends Activity { private static final String DEBUG_TAG = "Velocity"; ... private VelocityTracker mVelocityTracker = null; @Override public boolean onTouchEvent(MotionEvent event) { int index = event.getActionIndex(); int action = event.getActionMasked(); int pointerId = event.getPointerId(index); switch(action) { case MotionEvent.ACTION_DOWN: if(mVelocityTracker == null) { // Retrieve a new VelocityTracker object to watch the velocity of a motion. mVelocityTracker = VelocityTracker.obtain(); } else { // Reset the velocity tracker back to its initial state. mVelocityTracker.clear(); } // Add a user's movement to the tracker. mVelocityTracker.addMovement(event); break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); // When you want to determine the velocity, call // computeCurrentVelocity(). Then call getXVelocity() // and getYVelocity() to retrieve the velocity for each pointer ID. mVelocityTracker.computeCurrentVelocity(1000); // Log velocity of pixels per second // Best practice to use VelocityTrackerCompat where possible. Log.d("", "X velocity: " + VelocityTrackerCompat.getXVelocity(mVelocityTracker, pointerId)); Log.d("", "Y velocity: " + VelocityTrackerCompat.getYVelocity(mVelocityTracker, pointerId)); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // Return a VelocityTracker object back to be re-used by others. mVelocityTracker.recycle(); break; } return true; } Note: 注意,应当在ACTION_MOVE事件内部计算速度,不要在ACTION_UP内部计算,因为在ACTION_UP内部计算所得到的X与Y的速度值都是0.

优秀的个人博客,低调大师

《HarmonyOS 第一课之首席专家讲鸿蒙》重磅上线! 首席专家解码鸿蒙底层设计原理,赋能开发者迈向技术新高度

******************************************************************************************************************** ******************************************************************************************************************** ******************************************************************************************************************** ********************************************************************************************************************

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册