Android应用ViewDragHelper详解及部分源码浅析,2024年最新面试阿里P7岗

captureChildView(toCapture, pointerId);

return true;

}

return false;

}

可以看见,通过Callback的tryCaptureView()重写设置是否可以挪动该View,若可以挪动(返回true)则又调运了captureChildView()方法,继续看下captureChildView()方法源码:

public void captureChildView(View childView, int activePointerId) {

//暂存被捕获的这个View的相关信息及触摸信息

mCapturedView = childView;

mActivePointerId = activePointerId;

//通过Callback的onViewCaptured()方法回调当前View被捕获了

mCallback.onViewCaptured(childView, activePointerId);

//设置当前被捕获的子View状态为STATE_DRAGGING

//里面会通过mCallback.onViewDragStateChanged(state)回调告知状态

setDragState(STATE_DRAGGING);

}

到此一次mParentView自己消费事件,子View无拦截ACTION_DOWN的事件处理就彻底结束了。接着就是主流程的ACTION_MOVE事件了,这玩意由于mParentView的onTouchEvent消费了事件且没进行拦截ACTION_DOWN,所以一旦触发时就直接走进了processTouchEvent()方法里,下面是ACTION_MOVE代码:

public void processTouchEvent(MotionEvent ev) {

switch (action) {

case MotionEvent.ACTION_MOVE: {

//分两种情况,依赖上一个ACTION_DOWN事件

if (mDragState == STATE_DRAGGING) {

//ACTION_DOWN时CallBack的tryCaptureView()返回true时对mDragState赋值了STATE_DRAGGING,故此流程

final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);

final float x = MotionEventCompat.getX(ev, index);

final float y = MotionEventCompat.getY(ev, index);

final int idx = (int) (x - mLastMotionX[mActivePointerId]);

final int idy = (int) (y - mLastMotionY[mActivePointerId]);

//Step 1 重点!!!!!!

dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);

//Step 2 重点!!!!!!

saveLastMotion(ev);

} else {

//ACTION_DOWN时CallBack的tryCaptureView()返回false时对mDragState没进行赋值,故此流程

// Check to see if any pointer is now over a draggable view.

final int pointerCount = MotionEventCompat.getPointerCount(ev);

for (int i = 0; i < pointerCount; i++) {

final int pointerId = MotionEventCompat.getPointerId(ev, i);

final float x = MotionEventCompat.getX(ev, i);

final float y = MotionEventCompat.getY(ev, i);

final float dx = x - mInitialMotionX[pointerId];

final float dy = y - mInitialMotionY[pointerId];

//Step 3 重点!!!!!!

reportNewEdgeDrags(dx, dy, pointerId);

if (mDragState == STATE_DRAGGING) {

// Callback might have started an edge drag.

break;

}

final View toCapture = findTopChildUnder((int) x, (int) y);

//Step 4 重点!!!!!!

if (checkTouchSlop(toCapture, dx, dy) &&

tryCaptureViewForDrag(toCapture, pointerId)) {

break;

}

}

saveLastMotion(ev);

}

break;

}

}

}

可以看见,当ACTION_MOVE事件多次触发时该段代码会依据我们重写CallBack的代码分为可以托拽当前View和不能托拽两种情况。

我们先来看下不能托拽的情况,这种相对比较简单,可以看到上面代码的Step 3部分,reportNewEdgeDrags()方法是我们的重点,如下:

//在托拽时该方法会被多次调运

private void reportNewEdgeDrags(float dx, float dy, int pointerId) {

int dragsStarted = 0;

…//四个方向,省略三个

if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {

dragsStarted |= EDGE_BOTTOM;

}

if (dragsStarted != 0) {

mEdgeDragsInProgress[pointerId] |= dragsStarted;

//该方法只会被调运一次,checkNewEdgeDrag方法中有处理

mCallback.onEdgeDragStarted(dragsStarted, pointerId);

}

}

可以发现,当我们在ACTION_DOWN触发时重写CallBack的tryCaptureView()方法返回false(当前View不能托拽)且是边沿触摸时移动时首先会回调Callback的onEdgeDragStarted()方法通知自定义ViewGroup开始边沿托拽。接着我们把目光投向Step 4部分,if (checkTouchSlop(toCapture, dx, dy) && tryCaptureViewForDrag(toCapture, pointerId)) 判断是我们关注的核心,toCapture其实就是当前捕获的View(这个View在边沿模式时一般摸不到,所以其实拿到的不是想要的childView,所以一般我们会在回调onEdgeDragStarted()方法中重写手动调用captureChildView()方法,传入我们摸不到的View,这样就相当于绕过tryCaptureView将状态设置为STATE_DRAGGING了),下面我们先看下checkTouchSlop()方法,如下:

//检查手指移动的距离有没有超过触发处理移动事件的最短距离mTouchSlop

private boolean checkTouchSlop(View child, float dx, float dy) {

if (child == null) {

return false;

}

//如果想让某个View滑动,就要返回大于0,否则processTouchEvent()的ACTION_MOVE就不会调用tryCaptureViewForDrag()来捕获当前触摸的View

final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;

final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;

if (checkHorizontal && checkVertical) {

return dx * dx + dy * dy > mTouchSlop * mTouchSlop;

} else if (checkHorizontal) {

return Math.abs(dx) > mTouchSlop;

} else if (checkVertical) {

return Math.abs(dy) > mTouchSlop;

}

return false;

}

到此ACTION_MOVE不能托拽的情况就分析完毕了,我们再来看下可以托拽的情况,请看上面processTouchEvent()代码的Step 1,2部分,重点在于dragTo()方法,当我们正常捕获到View时ACTION_MOVE就不停的调用dragTo()对mCaptureView进行拖动,源码如下:

//left、top为mCapturedView.getLeft()+dx、mCapturedView.getTop()+dy,即期望目标坐标

//dx、dy为前后两次ACTION_MOVE移动的距离

private void dragTo(int left, int top, int dx, int dy) {

int clampedX = left;

int clampedY = top;

final int oldLeft = mCapturedView.getLeft();

final int oldTop = mCapturedView.getTop();

if (dx != 0) {

//重写固定横坐标移动到的位置

clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);

//这是View中定义的方法,实质是改变View的mLeft、mRight、mTop、mBottom达到移动View的效果,类似layout()方法的效果

//clampedX为新位置,oldLeft为旧位置,若想不动保证插值为0即可!!!!

mCapturedView.offsetLeftAndRight(clampedX - oldLeft);

}

if (dy != 0) {

//重写固定纵坐标移动到的位置

clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);

//这是View中定义的方法,实质是改变View的mLeft、mRight、mTop、mBottom达到移动View的效果,类似layout()方法的效果

mCapturedView.offsetTopAndBottom(clampedY - oldTop);

}

if (dx != 0 || dy != 0) {

final int clampedDx = clampedX - oldLeft;

final int clampedDy = clampedY - oldTop;

//当位置有变化时回调Callback的onViewPositionChanged方法实时通知

mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,

clampedDx, clampedDy);

}

}

到此可以发现ACTION_MOVE时如果可以托拽则会实时挪动View的位置,同时回调很多方法。具体移动到哪和范围由Callback的clampViewPositionHorizontal()和clampViewPositionVertical()来决定。到此一次ACTION_MOVE事件的触发处理也就分析完毕了。下面就该是松手时ACTION_UP或者ACTION_MOVE被mParentView的上级View拦截触发的ACTION_CANCEL事件了,他们与ACTION_MOVE类似,直接触发processTouchEvent()的ACTION_UP或者ACTION_CANCEL,如下:

public void processTouchEvent(MotionEvent ev) {

switch (action) {

case MotionEvent.ACTION_UP: {

if (mDragState == STATE_DRAGGING) {

releaseViewForPointerUp();

}

//重置所有的状态记录

cancel();

break;

}

case MotionEvent.ACTION_CANCEL: {

if (mDragState == STATE_DRAGGING) {

dispatchViewReleased(0, 0);

}

//重置所有的状态记录

cancel();

break;

}

}

}

可以看见,ACTION_UP和ACTION_CANCEL的实质都是重置资源和通知View触摸被释放,一个调运了releaseViewForPointerUp方法,另一个调运了dispatchViewReleased方法而已。那我们先看下releaseViewForPointerUp方法,源码如下:

private void releaseViewForPointerUp() {

//获得相关速率

mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);

final float xvel = clampMag(

VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),

mMinVelocity, mMaxVelocity);

final float yvel = clampMag(

VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),

mMinVelocity, mMaxVelocity);

//传入速率

dispatchViewReleased(xvel, yvel);

}

哎哟,握草,dispatchViewReleased(xvel, yvel)不就是ACTION_CANCEL传参为0调运的方法吗?也就是说ACTION_CANCEL与ACTION_UP逻辑一致,只是传入的速率不同而已。那我们就来看看这个方法,如下:

private void dispatchViewReleased(float xvel, float yvel) {

mReleaseInProgress = true;

//通知外部View被释放了

mCallback.onViewReleased(mCapturedView, xvel, yvel);

mReleaseInProgress = false;

//如果之前是STATE_DRAGGING状态,则复位状态为STATE_IDLE

if (mDragState == STATE_DRAGGING) {

// onViewReleased didn’t call a method that would have changed this. Go idle.

setDragState(STATE_IDLE);

}

}

可以看见dispatchViewReleased()方法主要就是通过CallBack通知手指松开了,同时将状态置位为STATE_IDLE。但是如果你留意该方法的注释和方法里的mReleaseInProgress变量的话,你一定会有疑惑。下面我就从注释和该变量出现的地方进行下简单分析,然后回过头就知道咋回事了,该方法注释提到了mReleaseInProgress变量与settleCapturedViewAt()和flingCapturedView()方法有关,那我们就来看下是啥关系,查看这两个方法的开头可以发现一个共性如下:

if (!mReleaseInProgress) {

throw new IllegalStateException("Cannot XXXXXXXXXX outside of a call to " +

“Callback#onViewReleased”);

}

我靠,默认mReleaseInProgress是false,在dispatchViewReleased()中CallBack回调onViewReleased()方法前把他置位了true,onViewReleased()后置位了false。这就是为啥注释里说唯一可以调用ViewDragHelper的settleCapturedViewAt()和flingCapturedView()的地方就是在Callback的onViewReleased()里,这下你指定就明白了,因为别的地方会抛出异常哇。

握草,这两方法为啥这么神奇,为啥只能在CallBack回调onViewReleased()中使用啊,我们先来分析一下就知道了,如下先看settleCapturedViewAt()方法源码:

//限制最终惯性滚动到的终极位置及滚动过去

public boolean settleCapturedViewAt(int finalLeft, int finalTop) {

//表明只能在CallBack回调onViewReleased()中使用

if (!mReleaseInProgress) {

throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " +

“Callback#onViewReleased”);

}

return forceSettleCapturedViewAt(finalLeft, finalTop,

(int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),

(int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId));

}

private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {

if (dx == 0 && dy == 0) {

// Nothing to do. Send callbacks, be done.

mScroller.abortAnimation();

setDragState(STATE_IDLE);

return false;

}

//直接用过Scroller滚动到指定位置

final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);

mScroller.startScroll(startLeft, startTop, dx, dy, duration);

//滚动时设置状态为STATE_SETTLING

setDragState(STATE_SETTLING);

return true;

}

再看下flingCapturedView()方法,如下:

//不限制终点,由松手时加速度决定惯性滚动过去,fling效果

public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {

if (!mReleaseInProgress) {

throw new IllegalStateException("Cannot flingCapturedView outside of a call to " +

“Callback#onViewReleased”);

}

//直接用过Scroller滚动

mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),

(int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),

(int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),

minLeft, maxLeft, minTop, maxTop);

//滚动时设置状态为STATE_SETTLING

setDragState(STATE_SETTLING);

}

到此确实很奇怪,为啥这两个方法要这么限制设计?我还是没想明白,请教大神指点一下!但是我全局搜索mScroller的相关调运又发现了新的方法,如下:

public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {

mCapturedView = child;

mActivePointerId = INVALID_POINTER;

boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);

if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {

// If we’re in an IDLE state to begin with and aren’t moving anywhere, we

// end up having a non-null capturedView with an IDLE dragState

mCapturedView = null;

}

return continueSliding;

}

这货也能达到上面受限的settleCapturedViewAt()方法的效果,额额额,我凌乱了,这么设计是为啥额,不清楚。不过到此一次完整的ViewDragHelper的触摸移动主流程就分析完成了,其他相关状态请自行分析,对我来说有这些就差不多够了,主流程掌握了基本就能明白他提供的相关API含义了,其它的遇到问题再现查即可。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

4 源码局部浅析总结

==============

经过上面的浅析与注释总结归纳如下总结方便日后使用;同时我们可以发现ViewDragHelper的本质其实涉及的知识点还是很多的,主要在事件处理上,不得不佩服该工具类考虑的强大。

//常用核心API归纳总结

public class ViewDragHelper {

//当前View处于空闲状态,静止

public static final int STATE_IDLE = 0;

//当前View处于托动状态中

public static final int STATE_DRAGGING = 1;

//当前View处于滚动惯性到settling坐标间的状态

public static final int STATE_SETTLING = 2;

//可托拽边缘方向常量

public static final int EDGE_LEFT = 1 << 0;

public static final int EDGE_RIGHT = 1 << 1;

public static final int EDGE_TOP = 1 << 2;

public static final int EDGE_BOTTOM = 1 << 3;

public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM;

//公有静态内部抽象回调类,当ViewDragHelper控制的ViewGroup中View变化时会被回调

public static abstract class Callback {

//当托拽状态变化时回调,譬如动画结束后回调为STATE_IDLE等

//state有三种状态,均以STATE_XXXX模式

public void onViewDragStateChanged(int state) {}

//当前被触摸的View位置变化时回调

//changedView为位置变化的View,left/top变化时新的x左/y顶坐标,dx/dy为从旧到新的偏移量

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}

//tryCaptureViewForDrag()成功捕获到子View时或者手动调用captureChildView()时回调

public void onViewCaptured(View capturedChild, int activePointerId) {}

//当子View被松手或者ACTION_CANCEL时时回调,xvel/yvel为离开屏幕时各方向每秒运动的速率,为px

public void onViewReleased(View releasedChild, float xvel, float yvel) {}

//当触摸ACTION_DOWN或ACTION_POINTER_DOWN边沿时回调

public void onEdgeTouched(int edgeFlags, int pointerId) {}

//返回true锁定edgeFlags对应的边缘,锁定后的边缘就不会回调onEdgeDragStarted()

public boolean onEdgeLock(int edgeFlags) {

return false;

}

//ACTION_MOVE且没有锁定边缘时触发

//可在此手动调用captureChildView()触发从边缘拖动子View,有点类似略过tryCaptureView返回false响应重定向其他View的效果

public void onEdgeDragStarted(int edgeFlags, int pointerId) {}

//寻找当前触摸点View时回调此方法

//如果需要改变子View的倒序遍历查询顺序则可改写此方法,譬如让重叠的下层View先于上层View被捕获

public int getOrderedChildIndex(int index) {

return index;

}

//返回给定子View在相应方向上可以被拖动的最远距离,默认为0,一般是可被挪动View时指定为指定View的大小等

public int getViewHorizontalDragRange(View child) {

return 0;

}

public int getViewVerticalDragRange(View child) {

return 0;

}

//传递当前触摸上的子View,如果需要当前触摸的子View进行拖拽移动就返回true,否则返回false

public abstract boolean tryCaptureView(View child, int pointerId);

//决定要拖拽的子View在所属方向上应该移动到的位置

//child为拖拽的子View,left为期望值,dx为挪动差值

public int clampViewPositionHorizontal(View child, int left, int dx) {

return 0;

}

public int clampViewPositionVertical(View child, int top, int dy) {

return 0;

}

}

//构造工厂方法,sensitivity用来调节mTouchSlop的值,默认一般传递1即可

//sensitivity越大,mTouchSlop越小,对滑动的检测就越敏感,譬如手指move多少才算滑动,否则忽略

public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {…}

//设置允许父View的某个边缘可以用来响应托拽

//相当于控制了CallBack对象的onEdgeTouched()和onEdgeDragStarted()方法是否被回调

public void setEdgeTrackingEnabled(int edgeFlags) {…}

//两个传递MotionEvent的方法

public boolean shouldInterceptTouchEvent(MotionEvent ev) {…}

public void processTouchEvent(MotionEvent ev) {…}

//主动在父View内捕获指定的子view用于拖曳,会回调tryCaptureView()

public void captureChildView(View childView, int activePointerId) {…}

//指定某个View自动滚动到指定的位置,初速度为0,可在任何地方调用

//如果这个方法返回true,那么在接下来动画移动的每一帧中都会回调continueSettling(boolean)方法,直到结束

public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {…}

//以松手前的滑动速度为初值,让捕获到的子View自动滚动到指定位置,只能在Callback的onViewReleased()中使用

//如果这个方法返回true,那么在接下来动画移动的每一帧中都会回调continueSettling(boolean)方法,直到结束

public boolean settleCapturedViewAt(int finalLeft, int finalTop) {…}

//以松手前的滑动速度为初值,让捕获到的子View在指定范围内fling惯性运动,只能在Callback的onViewReleased()中使用

//如果这个方法返回true,那么在接下来动画移动的每一帧中都会回调continueSettling(boolean)方法,直到结束

public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {…}

/**

  • 在整个settle状态中,这个方法会返回true,deferCallbacks决定滑动是否Runnable推迟,一般推迟

  • 在调用settleCapturedViewAt()、flingCapturedView()和smoothSlideViewTo()时,

  • 需要实现mParentView的computeScroll()方法,如下:

  • @Override

  • public void computeScroll() {

  • if (mDragHelper.continueSettling(true)) {
    
  •     ViewCompat.postInvalidateOnAnimation(this);
    
  • }
    
  • }

*/

public boolean continueSettling(boolean deferCallbacks) {…}

//设置与获取最小速率,一般保持默认

public void setMinVelocity(float minVel) {…}

public float getMinVelocity() {…}

//获取当前子View所处状态

public int getViewDragState() {…}

//返回可触摸反馈区域边缘大小,单位为px

public int getEdgeSize() {…}

//返回当前捕获的子View,如果没有则为null

public View getCapturedView() {…}

//获取当前拖曳的View的Pointer ID

public int getActivePointerId() {…}

//获取最小触发拖曳动作的灵敏度差值,单位为px

public int getTouchSlop() {…}

//类似ACTION_CANCEL事件的触发调运

public void cancel() {…}

//终止手势,结束动画滚动等,恢复初始STATE_IDLE状态

public void abort() {…}

}

可以看见,上面基本就是整个ViewDragHelper的相关public等可控制的API解释了,我们基本上可以通过他们的组合搞出各种自定义的控件来玩玩的。下面我们再粗略给出ViewDragHelper使用的流程,如下:

  1. 自定义ViewGroup里通过ViewDragHelper静态工厂方法create()创建实例并实现ViewDragHelper.CallBack抽象类。

  2. 在自定义ViewGroup的onInterceptTouchEvent()方法里调用并返回ViewDragHelper的shouldInterceptTouchEvent()方法,在onTouchEvent()方法里调用ViewDragHelper()的processTouchEvent()方法,且返回true(因为ACTION_DOWN时如果子View没有消费事件,我们需要在onTouchEvent()中返回true,否则收不到后续的事件,从而不会产生拖动等效果)。

  3. 依据自己需求实现ViewDragHelper.CallBack中相关方法即可。

  4. 至此已经实现了子View拖动效果,如果需要Fling或者惯性滚动效果则还需要实现自定义ViewGroup的computeScroll()方法进行手动刷帧。

以上就是使用ViewDragHelper的全过程,可以发现其真的很牛逼。

5 ViewDragHelper进阶实战与总结

===========================

扯了上面那么多,下面我们给出一个使用ViewDragHelper搞出来的东东,大家一看就知道他很牛逼了。如下是效果图(Ubuntu下面,GIF图Low爆了,请谅解):

这里写图片描述

该控件效果就不解释了,代码也不多,源码可以点我下载即可

至此整个ViewDragHelper进阶就介绍完了,如果你还认为不够的话,那就可以自行看看官方的DrawerLayout控件实现即可,可以把它当作该文的进阶实例,这里我就不再分析了,我们的项目中重写了DrawerLayout,因为一些特殊的交互需求。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
4868a3f0fd6ac81d625c.png)

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-ZiN12qgq-1712615265808)]

总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

[外链图片转存中…(img-dOLcBVwP-1712615265809)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-ivPNEk00-1712615265809)]

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Android Studio中导入Android源码,需按照以下步骤进行: 1. 下载Android源码:首先需要从Android官方网站(https://source.android.com/setup/build/downloading)上下载Android源码的zip文件。选择适合你设备版本的源码,并下载到本地。 2. 解压源码:将下载的源码zip文件解压到你希望存储源码的位置。 3. 导入源码项目:打开Android Studio,并点击“File”菜单,然后选择“New”,再选择“Import Project”。 4. 选择源码目录:在弹出的对话框中,浏览到你已经解压的源码目录,并选择其中的“build.gradle”文件,然后点击“OK”。 5. 等待构建:Android Studio会开始构建项目,可能需要一些时间来完成这个过程,具体时间取决于你的硬件性能和Android源码的大小。 6. 配置项目:构建完成后,你需要配置一些项目属性。你可以根据你的需求进行更改,比如修改Android版本、SDK版本、Gradle版本等。 7. 构建项目:在配置完成后,再次构建项目。你可以点击Android Studio工具栏中的“Build”按钮来完成构建。构建完成后,你就可以进行Android源码的浏览、修改和编译等操作了。 需要注意的是,导入Android源码是一个相对复杂的过程,并且需要一定的硬件配置和软件环境支持。同时,也需要有一定的Android开发和编译经验。因此,在遇到问题时,你可以参考官方文档或在开发者社区中寻求帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值