2024年Android最新Android仿人人客户端(v5,高级安卓工程师面试题答案

写在最后

本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!

最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:

对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

Android架构师之路很漫长,一起共勉吧!

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

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


public class TestActivity extends Activity {

    

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        

        ParentContainer mSlideContainer = new ParentContainer(this);

        

        ChildContainer childContainer = new ChildContainer(this);

        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);

        mSlideContainer.addView(childContainer, params);

        

        setContentView(mSlideContainer);

    }

    

}

a. 默认情况下,单击子View中的Button按钮,LogCat打印Log如下:

修改父容器的onInterceptTouchEvent()返回值为false,单击子View的按钮,打印Log与默认值一样。跟踪源码发现其实默认返回值就是false。修改返回值为true,单击子View的按钮,LogCat打印Log如下:

结论:父容器中onInterceptTouchEvent()方法的返回值为true时,表示将事件交给ViewGroup自己的onTouchEvent()去处理;返回值为false时,表示将事件交给ViewGroup的子View的onInterceptTouchEvent()去处理。(默认的处理方式)

b. 父容器使用默认的值,修改子View的onTouchEvent()方法返回值为false,单击子View,LogCat打印Log如下:

修改子View的onTouchEvent()方法返回值为true,单击子View,LogCat打印Log如下:

结论:父容器使用默认的值,修改子View的onTouchEvent()方法返回值为false,表示将事件交父View处理;修改子View的onTouchEvent()方法返回值为true,表示该事件子View自己已经处理了,到这里终止。

4、在父容器中,拦截用户触屏手势后,想交给父容器自己去处理,或者是想交给父容器里的某个子View去处理,应该怎么实现,通过上面的讲解,我想大家已经明白了,决定事件的传递顺序或在那个View里终止传递,是通过ViewGroup中的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法的返回值来决定的。接下来开始聊如何实现父容器中的子View的左右滑动(滚动),这里就用到了Scroller和VelocityTracker两个类。

a. 为什么要用Scroller类? 如果实现想把一个View偏移至指定坐标(x,y)处,利用View类提供的scrollTo()方法直接调用就可以了。但是View类的scrollTo()方法是非常迅速的将View从一个坐标点(20, 0)移到另一个坐标点(300, 0),而没有对这个偏移过程有任何控制,对用户而言这件事发生的很突然,用户体验不好。而Scroller类提供的startScroll()方法,在偏移过程中添加了动画,提升了用户体验。因此我们选择使用Scroller类的对象来实现View的偏移。

b. VelocityTracker类,主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。  用addMovement(MotionEvent)函数将Motion event加入到VelocityTracker类实例中。你可以使用getXVelocity() 或getXVelocity()获得横向和竖向的速率到速率时,但是使用它们之前请先调用computeCurrentVelocity(int)来初始化速率的单位 。

关于computeCurrentVelocity(int units, float maxVelocity) 方法的参数列表解释:

int  unitis表示速率的基本时间单位。unitis值为1的表示是,一毫秒时间单位内运动了多少个像素, unitis值为1000表示一秒(1000毫秒)时间单位内运动了多少个像素。

float  maxVelocity表示速率的最大值。

二、按上面的讲解思路编码实现:

1、滑动方式实现:

只在父容器的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法中,添加MotionEvent.ACTION_MOVE的事件处理。

父容器类代码如下:


package com.everyone.android.widget;



import android.content.Context;

import android.util.Log;

import android.util.TypedValue;

import android.view.MotionEvent;

import android.view.VelocityTracker;

import android.view.ViewConfiguration;

import android.widget.RelativeLayout;

import android.widget.Scroller;



/**

 * 功能描述:手指在屏幕上左右滑动时,该类的实例负责其的子View的左右偏移(滚动)

 * @author android_ls

 */

public class ScrollerContainer extends RelativeLayout {



    private static final String TAG = "ScrollerContainer";



    private Scroller mScroller;



    private VelocityTracker mVelocityTracker;



    /**

     * 手柄(手把)的宽度

     */

    private int mHandlebarWidth;



    /**

     * 一秒时间内移动了多少个像素

     */

    private float mVelocityValue;

    

    /**

     * 在偏移过程中,动画持续的时间

     */

    private static final int ANIMATION_DURATION_TIME = 300;

    

    public ScrollerContainer(Context context) {

        super(context);



        mScroller = new Scroller(context);

        mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());

    }



    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");



        mVelocityTracker = VelocityTracker.obtain();

        mVelocityTracker.addMovement(ev);

        

        switch (ev.getAction()) {

        case MotionEvent.ACTION_DOWN:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_DOWN");

            

            break;



        case MotionEvent.ACTION_MOVE:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_MOVE");

            

            mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());

            mVelocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;

            Log.d(TAG, "onInterceptTouchEvent():  mVelocityValue = " + mVelocityValue);

            

            if (mVelocityValue > 300) {

                return true;

            }

            

            break;



        case MotionEvent.ACTION_UP:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_UP");

            break;

        case MotionEvent.ACTION_CANCEL:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_CANCEL");

            break;

        default:

            break;

        }

        

        return super.onInterceptTouchEvent(ev);

    }



    @Override

    public boolean onTouchEvent(MotionEvent event) {

        Log.e(TAG, "ParentContainer : onTouchEvent()");



        float x = event.getX();

        

        switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:

            Log.i(TAG, "onTouchEvent():  ACTION_DOWN");

            

            break;



        case MotionEvent.ACTION_MOVE:

            Log.i(TAG, "onTouchEvent():  ACTION_MOVE");

            

            getChildAt(1).scrollTo(-(int)x, 0);

            break;



        case MotionEvent.ACTION_UP:

            Log.i(TAG, "onTouchEvent():  ACTION_UP");

            

         

           float width = getWidth();

           float halfWidth = width / 2;

           

           Log.i(TAG, "onTouchEvent():  ACTION_UP x = " + x + "\t halfWidth = " + halfWidth);

           

           int scrollX = getChildAt(1).getScrollX();

           

           if ( x < halfWidth) {

               Log.i(TAG, "onTouchEvent():  ACTION_UP 向左滑动");

               

                mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);

                invalidate();

            } else if ( x > halfWidth){

                Log.i(TAG, "onTouchEvent():  ACTION_UP 向右滑动");

              

                int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);

                mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);

                invalidate();

            }

            

            break;

        case MotionEvent.ACTION_CANCEL:

            Log.i(TAG, "onTouchEvent():  ACTION_CANCEL");

            break;

        default:

            break;

        }

        

        return super.onTouchEvent(event);

    }



    @Override

    public void computeScroll() {

        // super.computeScroll();

        

        if(mScroller.computeScrollOffset()){

            this.getChildAt(1).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

            this.postInvalidate();

        }

    }



}



子容器就是两个继承自FrameLayout的Layout,源码就不贴了。

测试类代码如下:


package com.everyone.android.ui;



import android.app.Activity;

import android.os.Bundle;

import android.view.ViewGroup.LayoutParams;



import com.everyone.android.widget.FreshNewsLayout;

import com.everyone.android.widget.LeftPanelLayout;

import com.everyone.android.widget.ScrollerContainer;



public class TestActivity extends Activity {



    /**

     * 左侧面板

     */

    private LeftPanelLayout mLeftPanelLayout;



    /**

     * 新鲜事

     */

    private FreshNewsLayout mFreshNewsLayout;



    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        ScrollerContainer mSlideContainer = new ScrollerContainer(this);

        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);



        mLeftPanelLayout = new LeftPanelLayout(this.getApplicationContext());

        mFreshNewsLayout = new FreshNewsLayout(this.getApplicationContext());



        mSlideContainer.addView(mLeftPanelLayout, params);

        mSlideContainer.addView(mFreshNewsLayout, params);



        setContentView(mSlideContainer);

    }



}

运行效果图如下:

2、单击方式实现:

左右滑动实现子View的滚动,有一个临界值,一秒时间内移动了的像素数要大于某个预设的值,才会触动相应事件处理器。那么为了用户体验好点,我们提供另外一种操作方式,那就是单击事件。假设ViewGroup中有两个子View A和B,B处于A上面,两个子View是叠在一起的。默认显示的是B,并占据着整个手机屏幕,A是看不见的。为了能看见A,并且可以操作,我们需要把B视图(子View)移动一定单位,可以在B视图中添加子View(Button)并绑定事件监听器。当用户点击B视图中的特定子View(Button)时,让B视图偏移一定单位,我么就能看见A视图;当B处于A上并偏移了一定的单位,这时单击B,实现B视图移动到回去(恢复默认显示)。

a. 单击B,实现B视图移动到回去(恢复默认显示),代码如下:


    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");



        mVelocityTracker = VelocityTracker.obtain();

        mVelocityTracker.addMovement(ev);

        

        switch (ev.getAction()) {

        case MotionEvent.ACTION_DOWN:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_DOWN");

            

            int x = (int) ev.getX();

            int width = getWidth();

            if(x >= (width - mHandlebarWidth)){

                isClick = true;

            }

            

            break;



        case MotionEvent.ACTION_MOVE:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_MOVE");

            

            mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());

            mVelocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;

            Log.d(TAG, "onInterceptTouchEvent():  mVelocityValue = " + mVelocityValue);

            

            if (mVelocityValue > 300) {

                return true;

            }

            

            break;



        case MotionEvent.ACTION_UP:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_UP");

            

            if (isClick) {

                isClick = false;

                 int scrollX = getChildAt(1).getScrollX();

                 mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);                                                        invalidate();

            }

            

            break;

        case MotionEvent.ACTION_CANCEL:

            Log.i(TAG, "onInterceptTouchEvent():  ACTION_CANCEL");

            break;

        default:

            break;

        }

        

        return super.onInterceptTouchEvent(ev);

    }

b. 当用户点击B视图中的特定子View(Button)时,让B视图偏移一定单位。在父容器中添加滑动事件监听器和向右滑动的实现,代码如下:


  /**

     * 向右滑动View,让左侧操作面饭可见

     */

    public void slideToRight() {

        float width = getWidth();

        int scrollX = getChildAt(1).getScrollX();

        int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);

        mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);

        invalidate();

    }



### 关于面试的充分准备

一些基础知识和理论肯定是要背的,要理解的背,用自己的语言总结一下背下来。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,**现在高级工程师还是比较缺少的**,我能明显感觉到国庆后多了很多高级职位,所以努力让自己成为高级工程师才是最重要的。

好了,希望对大家有所帮助。

接下来是整理的一些Android学习资料,**有兴趣的朋友们可以关注下我免费领取方式**。

**①Android开发核心知识点笔记**

**②对标“阿里 P7” 40W+年薪企业资深架构师成长学习路线图**

![](https://img-blog.csdnimg.cn/img_convert/4c9b9ec4ad2b58c57c04aff67cd5949a.webp?x-oss-process=image/format,png)

**③面试精品集锦汇总**

![](https://img-blog.csdnimg.cn/img_convert/a5524673f166cbc9ef85c122d219f243.webp?x-oss-process=image/format,png)

**④全套体系化高级架构视频**

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

![](https://img-blog.csdnimg.cn/img_convert/22cdd05267650c72bf80a107c65cea59.webp?x-oss-process=image/format,png)



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

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

的一些Android学习资料,**有兴趣的朋友们可以关注下我免费领取方式**。

**①Android开发核心知识点笔记**

**②对标“阿里 P7” 40W+年薪企业资深架构师成长学习路线图**

[外链图片转存中...(img-ph806MFZ-1715710299142)]

**③面试精品集锦汇总**

[外链图片转存中...(img-hcpscE6k-1715710299142)]

**④全套体系化高级架构视频**

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

[外链图片转存中...(img-FfgTsTcn-1715710299142)]



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

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

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值