Android学习笔记——基础介绍

本文深入解析Android UI开发,涵盖四大组件(Activity、Service、BroadcastReceiver、ContentProvider)的用途和Intent交互。重点讲解Activity的创建、跳转与生命周期,以及基础和高级UI组件,如View、ViewGroup、屏幕适配、事件传递、ListView和RecyclerView的使用。ListView的复用机制和RecyclerView的灵活性是提高性能的关键。最后,通过示例代码展示了如何在实践中应用这些知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

​​​​一、Android 的四大组件

二、UI的基石——Activity

1.创建Activity

2.跳转与关闭

3.Activity生命周期

4.Intent相关

 三、基础UI组件

1.View和ViewGroup

2.view的继承关系

 3.屏幕适配

3.1 基本概念

3.2 相对布局

3.3 点九图——Nine Patch图

四、事件传递

 五、高级UI组件

1.ListView

1.1 简介

 1.2 ListView复用机制

1.3 代码示例

2 RecyclerView

2.1 RecyclerView简介

 2.2 RecyclerView代码例子

③编写Activity

 2.3 Adapter:

2.4 复用(三级缓存 + 自定义缓存):

3 ListView和RecyclerView对比


​​​​一、Android 的四大组件

  • Activity: 包含用户界面的组件, 主要用于与用户交互
  • Service: 后台运行的解决方案,不需要用户交互的长期运行任务
  • BroadcastReceiver: 系统级的发布-订阅机制
  • ContentProvider: 不同应用程序之间共享数据
    Intent: 各组件之间进行交互、传递意图(动作和数据), 用户的一切行为可以由多个组件无缝协同支持。
    eg:
    Tom: “ 我把上次我们聚会的照片发你 mail ”
    Jack: “ok”,心想: 照片把我拍的太丑了,我要P一下,再发到 ….
    以上操作需要以下动作:
    ①在邮件app浏览图片
    ②分享到图片编辑app
    ③编辑修改图片
    ④分享到社交app
    完成以上动作,Android和PC交互对比:
    PC: 应用之间无关联,通过桌面+鼠标拖拽进行交互;
    Android: 没有“桌面+鼠标”,“应用”之间直接交互( 基于组件开发 : 通过四大组件和 Intent 进行交互 )

二、UI的基石——Activity

1.创建Activity

  • 创建XXActivity.java,继承Activity
  • 在AndroidManifest.xml里面注册
  • 编写布局xml
  • 在onCreate里面setContent(xml)

Intent: 四大组件中通讯的对象(启动Activity、Service、传递Broadcast等)
● Bundle: Intent中的“ 数据 ”,一个Map类型的KV包装类,封装了序列化操作
● Parcel: Android中的序列化方式,可用于跨进程传输

2.跳转与关闭

使用startActivity()进行跳转,使用Intent传递数据;

使用finish()退出Activity或者点击Back按键退出Acticity(默认内部也会调用到finish);

3.Activity生命周期

回调函数:
    ● onCreate、onDestroy
    ● onStart、onStop(当界面消失时,比如跳转至别的页面,stop)
    ● onResume、onPause(当出现透明主题时,比如焦点对话框等,pause)
    ● onRestart
特殊情况:
  • ​​​​onSaveInstanceState和onRestoreInstanceState(保存之前状态)

4.Intent相关

  • 显示Intent:明确了要指定启动的组件

 表示从当前界面跳转至XmlActicity

  • 隐示Intent:没有明确指定的组件,会把命中特定规则的组件都唤起然后选择

 比如要打开百度首页,只设置网页和动作,手机下方弹出所有的浏览器供用户选择。

  • startActivityForResult:启动一个新的Activity之后需要有返回值
模拟微信授权,腾讯后台会返回一个token值。

 三、基础UI组件

1.View和ViewGroup

  • 常见控件View

        TextView、Button、EditText、ImageView

  • 常见布局ViewGroup

        线性布局LinearLayout 、相对布局RelativeLayout 、帧布局FrameLayout

线性布局

 相对布局(相对于xx的位置)

帧布局 (写在越下面,则界面元素相对位于上面)

2.view的继承关系

 3.屏幕适配

3.1 基本概念

• px (像素)
• 分辨率:屏幕横向、纵向像素点,宽 x 高,如320 x 480
• 屏幕尺寸:屏幕对角线物理尺寸,单位英寸
• dpi (屏幕像素密度):每英寸像素点数
• dp (密度无关像素):保证不同屏幕像素屏幕上显示效果一致,Android特有
长度单位, 1dp = (dpi / 160) * 1px
• sp (独立比例像素):字体专用,其它和dp一样。区别是Android 自定义字体大
小功能,正常字体时 1sp = 1dp,超大、超小字体时 1sp 大于或者小于1dp
比如用px为单位时,当屏幕分辨率不同时,显示效果不同,而下方用dp作为单位是,显示效果没有变化。

3.2 相对布局

match-parent(与父容器大小一致)
wrap_content(与内容大小一致)

3.3 点九图——Nine Patch图

以.9.png为拓展名的png图片文件,可拉伸位图,适用于不定长内容背景(eg:聊天气泡)。

3.4 xxhpi xhpi hpi
根据屏幕密度对应目录下获取图片
        没有对应目录时:就近高密度 > 就近低密度
         没有对应目录时:图片自动根据当前密度进行缩放
eg:

 当前手机为xxhdpi,但是文件中最高密度只有xhdpi的图片,则从高到底查找,即xxhdpi中没有,则用xhdpi文件的图片。国内企业通常选择一个稍大密度的目录存放一份图片(减小apk包大小),原因:缩放不会失真,扩大可能会有一些失真。

四、事件传递

● 触摸事件(MotionEvent):ACTION_DOWN、ACTION_MOVE、ACTION_UP

● 事件分发(Dispatch):dispatchTouchEvent

● 事件拦截 (Intercept):onInterceptTouchEvent

● 事件消费 (Consume):onTouchEvent

eg: 

没当一个触摸事件到来,都是从 上往下执行分发,从下往上执行消费事件,即从Activity往View分发,如果有拦截事件则停止往下分发,回传执行消费事件,没有消费成功(消费事件的bool值为false时)往上回传给上一层消费,如果到最高层的Activity也没有消费,则该事件就被抛弃。

 五、高级UI组件

1.ListView

1.1 简介

一个显示可滚动项目的视图组件,系统使用Adapter(适配器)将列表项目插入列表,适配器从来源提取内容。在Android 5.0版本之后提供了RecycleView去替代ListView和GridView,提供了一种插拔式的体验,即模块化。

 1.2 ListView复用机制

采用MVC模式, 模型(model)-视图(view)-控制器 (controller)

M:model指要显示的数据,如封装数据的cursor,array等
V: view,就是listView用来显示封装好的数据
C: controller,就是adaptor,用来控制数据如何向listview中显示

     ps:引用自代码丶如风ActivityView其实就是在UI屏幕上可见的视图(onScreenView),也是与用户进行交互的View,那么这些View会通过RecycleBin直接存储到mActivityView数组当中,以便为了直接复用,那么当我们滑动ListView的时候,有些View被滑动到屏幕之外(offScreen) View,那么这些View就成为了ScrapView,也就是废弃的View,已经无法与用户进行交互了,这样在UI视图改变的时候就没有绘制这些无用视图的必要了。他将会被RecycleBin存储到mScrapView数组当中,但是没有被销毁掉,目的是为了二次复用,也就是间接复用。当新的View需要显示的时候,先判断mActivityView中是否存在,如果存在那么我们就可以从mActivityView数组当中直接取出复用,也就是直接复用,否则的话从mScrapView数组当中进行判断,如果存在,那么二次复用当前的视图,如果不存在,那么就需要inflate View了。


        如果数据源没有变化的时候,会从mActivityView数组中判断是否存在可以直接复用的View,举个例子,比如ListView一页可以显示10条数据,那么在这个时候滑动一个Item的距离,也就是把position = 0的Item移除屏幕,将position = 10 的Item移入屏幕,那么position = 1的Item是不是就直接能够从mActivityView数组中拿到呢?这是可以的,我们在第一次加载Item数据的时候,已经将position = 0~9的Item加入到了mActivityView数组当中,那么在第二次加载的时候,由于position = 1 的Item还是ActivityView,那么这里就可以直接从数组中获取,然后重新布局。这里也就表示的是Item的直接复用。        如果我们在mActivityView数组中获取不到position对应的View,那么就尝试从mScrapView废弃View数组中尝试去获取,当position = 0的Item被移除屏幕的时候,首先会Detach让View和视图进行分离,清空children,然后将废弃View添加到mScrapView数组当中,当加载position = 10的Item时,mActivityView数组肯定是没有的,也就无法获取到,同样mScrapView中也是不存在postion = 10与之对应的废弃View,就是mScrapView数组只有mScrapView[0]这一项数据,肯定是没有mScrapView[10]这项数据的,肯定是从Adapter中的getView方法获取新的数据,其实并不是这样,虽然mScrapView中虽然没有与之对应的废弃View,但是会返回最后一个缓存的View传递给convertview。那么也就是将mScrapView[0]对应的View返回。注意一种情况:比如说还是一页的Item,但是position = 0的Item没有完全滑动出UI,position = 10的Item没有完全进入到UI的时候,那么position = 0的Item不会被detach掉,同样不会被加入到废弃View数组,这时mScrapView是空的,没有任何数据,那么position = 10的Item即无法从mActivityView中直接复用View,因为是第一次加载。mActivityView[10]是不存在的,同时mScrapView是空的,因此position = 10的Item只能重新生成View,也就是从getView方法中inflate。

1.3 代码示例

①布局

适配器的代码: 

自定义一个适配器,继承自BaseAdapter,重写四个方法,getCount、getItem、getItemId、getView

public class ListBaseAdapter extends BaseAdapter {

    private static final int NUM_LIST_ITEMS = 100;

    // 数据集总个数
    @Override
    public int getCount() {
        return NUM_LIST_ITEMS;
    }

    // 根据position获取数据对象
    @Override
    public Object getItem(int position) {
        return null;
    }

    // 根据position获取数据对象的id
    @Override
    public long getItemId(int position) {
        return 0;
    }

    // 对应position的item数据展示样式view
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        // 查看convertView是否为空,如果为空则重新创建,使用setTag方法实现保留复用
        if (convertView == null) {
            holder = new ViewHolder();
            convertView = View.inflate(parent.getContext(), R.layout.number_list_item, null);
            holder.listItemNumberView = (TextView) convertView.findViewById(R.id.tv_item_number);
            holder.viewHolderIndex = (TextView) convertView.findViewById(R.id.tv_view_holder_instance);
            convertView.setTag(holder);
        } else {
            // 复用机制,直接使用viewholder,无需每次都find id
            holder = (ViewHolder) convertView.getTag();
        }

        // 更新item对应的数据值
        holder.listItemNumberView.setText(String.valueOf(position));
        holder.viewHolderIndex.setText(String.format("ViewHolder index: %s", position));
        int backgroundColorForViewHolder = ColorUtils.
                getViewHolderBackgroundColorFromInstance(convertView.getContext(), position % 10);
        convertView.setBackgroundColor(backgroundColorForViewHolder);
        return convertView;
    }

    private static class ViewHolder {
        private TextView viewHolderIndex;
        private TextView listItemNumberView;
    }
}

③编写Activity,设置我们自己写的适配器。

public class ListViewActivity extends AppCompatActivity {
    private Toast mToast;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        ListView listView = (ListView) findViewById(R.id.list_numbers);
        listView.setAdapter(new ListBaseAdapter());
        listView.setDivider(null);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mToast != null) {
                    mToast.cancel();
                }
                String toastMessage = "Item #" + position + " clicked.";
                mToast = Toast.makeText(ListViewActivity.this, toastMessage, Toast.LENGTH_LONG);
                mToast.show();
            }
        });
    }
}

注意: 在ListView中可以实现Click很简单,直接调用setOnItemClickListner,如上诉代码所示。

2 RecyclerView

2.1 RecyclerView简介

相比于ListView只能做上下滑动的视图,而RecyclerView可以灵活地使用包括线性布局(支持纵向、横向滑动)、网络布局、瀑布流。

 2.2 RecyclerView代码例子

①布局

 ②编写适配器

核心的方法:onCreateViewHolder、onBindViewHolder、getItemCount(类似于ListView中的getCount)

在ListView中,需要我们手动去实现ViewHolder,而在RecyclerView中将ViewHolder的方法接口暴露出来。

如果没有ViewHolder则通过onCreateViewHolder创建,有则调用onBindViewHolder在onBindViewHolder中通过我们自己写的bind函数设置相应的视图。

/**
 * 适配器
 */
public class GreenAdapter extends RecyclerView.Adapter<GreenAdapter.NumberViewHolder> {

    private static final String TAG = "GreenAdapter";

    private int mNumberItems;

    private final ListItemClickListener mOnClickListener;

    private static int viewHolderCount;

    public GreenAdapter(int numListItems, ListItemClickListener listener) {
        mNumberItems = numListItems;
        mOnClickListener = listener;
        viewHolderCount = 0;
    }


    /*
     * 一般会预留2~4个ViewHolder,off screen的数量由mCachedSize来决定
     *
     * The number of ViewHolders that have been created. Typically, you can figure out how many
     * there should be by determining how many list items fit on your screen at once and add 2 to 4
     * to that number. That isn't the exact formula, but will give you an idea of how many
     * ViewHolders have been created to display any given RecyclerView.
     *
     * Here's some ASCII art to hopefully help you understand:
     *
     *    ViewHolders on screen:
     *
     *        *-----------------------------*
     *        |         ViewHolder index: 0 |
     *        *-----------------------------*
     *        |         ViewHolder index: 1 |
     *        *-----------------------------*
     *        |         ViewHolder index: 2 |
     *        *-----------------------------*
     *        |         ViewHolder index: 3 |
     *        *-----------------------------*
     *        |         ViewHolder index: 4 |
     *        *-----------------------------*
     *        |         ViewHolder index: 5 |
     *        *-----------------------------*
     *        |         ViewHolder index: 6 |
     *        *-----------------------------*
     *        |         ViewHolder index: 7 |
     *        *-----------------------------*
     *
     *    Extra ViewHolders (off screen)
     *
     *        *-----------------------------*
     *        |         ViewHolder index: 8 |
     *        *-----------------------------*
     *        |         ViewHolder index: 9 |
     *        *-----------------------------*
     *        |         ViewHolder index: 10|
     *        *-----------------------------*
     *        |         ViewHolder index: 11|
     *        *-----------------------------*
     *
     *    index:12 from where?
     *
     *    Total number of ViewHolders = 12
     *
     *
     *    不做特殊处理:最多缓存多少个ViewHolder N(第一屏可见) + 2 mCachedSize + 5*itemType RecyclePool
     *
     *    找到position一致的viewholder才可以复用,新的位置由于position不一致,所以不能复用,重新创建新的
     *    这也是为什么 mCachedViews一开始缓存的是0、1    所以 8、9、10需要被创建,
     *    那为什么10 和 11也要被创建?
     *
     *    当view完全不可见的时候才会被缓存回收,这与item触发getViewForPosition不同,
     *    当2完全被缓存的时候,实际上getViewForPosition已经触发到11了,此时RecyclePool有一个viewholder(可以直接被复用)
     *    当12触发getViewForPosition的时候,由于RecyclePool里面有,所以直接复用这里的viewholder
     *    问题?复用的viewholder到底是 0 1 2当中的哪一个?
     *
     *
     *    RecycleView 对比 ListView 最大的优势,缓存的设计,减少bindView的处理
     */

    @NonNull
    @Override
    public NumberViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
        Context context = viewGroup.getContext();
        int layoutIdForListItem = R.layout.number_list_item;
        LayoutInflater inflater = LayoutInflater.from(context);
        boolean shouldAttachToParentImmediately = false;

        View view = inflater.inflate(layoutIdForListItem, viewGroup, shouldAttachToParentImmediately);
        NumberViewHolder viewHolder = new NumberViewHolder(view);

        viewHolder.viewHolderIndex.setText("ViewHolder index: " + viewHolderCount);

        int backgroundColorForViewHolder = ColorUtils
                .getViewHolderBackgroundColorFromInstance(context, viewHolderCount);
        viewHolder.itemView.setBackgroundColor(backgroundColorForViewHolder);

        Log.d(TAG, "onCreateViewHolder: number of ViewHolders created: " + viewHolderCount);
        viewHolderCount++;
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull NumberViewHolder numberViewHolder, int position) {
        Log.d(TAG, "onBindViewHolder: #" + position);
        numberViewHolder.bind(position);
    }

    @Override
    public int getItemCount() {
        return mNumberItems;
    }

    public class NumberViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private final TextView viewHolderIndex;
        private final TextView listItemNumberView;

        public NumberViewHolder(@NonNull View itemView) {
            super(itemView);
            listItemNumberView = (TextView) itemView.findViewById(R.id.tv_item_number);
            viewHolderIndex = (TextView) itemView.findViewById(R.id.tv_view_holder_instance);
            itemView.setOnClickListener(this);
        }

        public void bind(int position) {
            listItemNumberView.setText(String.valueOf(position));


//            viewHolderIndex.setText(String.format("ViewHolder index: %s", getAdapterPosition()));
//            int backgroundColorForViewHolder = ColorUtils.
//                    getViewHolderBackgroundColorFromInstance(itemView.getContext(), getAdapterPosition() % 10);
//            itemView.setBackgroundColor(backgroundColorForViewHolder);
        }

        @Override
        public void onClick(View v) {
            int clickedPosition = getAdapterPosition();
            if (mOnClickListener != null) {
                mOnClickListener.onListItemClick(clickedPosition);
            }
        }
    }

    // 自定义接口
    public interface ListItemClickListener {
        void onListItemClick(int clickedItemIndex);
    }
}

③编写Activity

public class RecycleViewActivity extends AppCompatActivity implements GreenAdapter.ListItemClickListener {

    private static final String TAG = "wangyi";
    private static final int NUM_LIST_ITEMS = 100;

    private GreenAdapter mAdapter;
    private RecyclerView mNumbersListView;

    private Toast mToast;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycleview);
        mNumbersListView = findViewById(R.id.rv_numbers);

        // LinearLayoutManager布局管理,定义布局样式
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mNumbersListView.setLayoutManager(layoutManager);
        /*
         * Use this setting to improve performance if you know that changes in content do not
         * change the child layout size in the RecyclerView
         */
        mNumbersListView.setHasFixedSize(true);

        /*
         * The GreenAdapter is responsible for displaying each item in the list.
         */
        mAdapter = new GreenAdapter(NUM_LIST_ITEMS, this);

        mNumbersListView.setAdapter(mAdapter);
        mNumbersListView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            // 最后一个完全可见项的位置
            private int lastCompletelyVisibleItemPosition;

            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                int visibleItemCount = layoutManager.getChildCount();
                int totalItemCount = layoutManager.getItemCount();
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (visibleItemCount > 0 && lastCompletelyVisibleItemPosition >= totalItemCount - 1) {
                        Toast.makeText(RecycleViewActivity.this, "已滑动到底部!,触发loadMore", Toast.LENGTH_SHORT).show();
                    }
                }
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                if (layoutManager instanceof LinearLayoutManager) {
                    lastCompletelyVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
                }
                Log.d(TAG, "onScrolled: lastVisiblePosition=" + lastCompletelyVisibleItemPosition);
            }
        });
    }

    @Override
    public void onListItemClick(int clickedItemIndex) {
        Log.d(TAG, "onListItemClick: ");
        if (mToast != null) {
            mToast.cancel();
        }
        String toastMessage = "Item #" + clickedItemIndex + " clicked.";
        mToast = Toast.makeText(this, toastMessage, Toast.LENGTH_LONG);

        mToast.show();
    }
}

注意:在RecyclerView中默认没有像ListView中的点击方法。做法如下:

  • 在Activity中设置Adapter的时候会把我们自己定义的Listener传入。

  • 在Adapter中自定义一个onListItemClick的接口

 2.3 Adapter:

  • ItemView 复用Google已经搞定,不用再setTag
  • ViewHolder的编写规范化
        在实现Adapter的时候,我们一般会加上ViewHolder这个东西,ViewHolder和复用机制和原理是无关的,他的主要目的是持有Item中控件的引用,从而减少findViewById()的次数,因为findViewById()方法也是会影响效率的,因此在复用的时候他起的作用是这个,减少方法执行次数增加效率。

2.4 复用(三级缓存 + 自定义缓存):

3 ListView和RecyclerView对比

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值