Android_study2

https://www.runoob.com/w3cnote/android-tutorial-intro.html菜鸟教程

https://guolin.blog.csdn.net/ 郭霖

目录

Adapter的使用

Adapter是用来帮助填充数据的中间桥梁,简单点说就是将各种数据以合适的形式显示到view上,提供给用户看。

MVC模式的简单理解

在开始学习Adapter之前我们要来了解下这个MVC模式概念: 举个例子:大型的商业程序通常由多人一同开发完成,比如有人负责操作接口的规划与设计, 有人负责程序代码的编写如果要能够做到程序项目的分工就必须在程序的结构上做适合的安排 ,如果,接口设计与修改都涉及到程序代码的改变的话,那么两者的分工就会造成执行上的困难 良好的程序架构师将整个程序项目划分为如图的三个部分:

img

关系图解析:

  • Model:通常可以理解为数据,负责执行程序的核心运算与判断逻辑,通过view获得用户 输入的数据,然后根据从数据库查询相关的信息,最后进行运算和判断,再将得到的结果交给view来显示
  • view:用户的操作接口,说白了就是GUI,应该使用哪种接口组件,组件间的排列位置与顺序都需要设计
  • Controller:控制器,作为model与view之间的枢纽,负责控制程序的执行流程以及对象之间的一个互动

而这个Adapter则是中间的这个Controller的部分: Model(数据) —> Controller(以什么方式显示到)—> View(用户界面) 这就是简单MVC组件的简单理解!

Adapter概念解析

官方文档:Adapter

首先我们来看看他的继承结构图:

img

上面就是Adapter以及继承结构图了,接着我们介绍一下实际开发中还用到的几个Adapter吧!

  • BaseAdapter:抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个Adapter!
  • ArrayAdapter:支持泛型操作,最简单的一个Adapter,只能展现一行文字~
  • SimpleAdapter:同样具有良好扩展性的一个Adapter,可以自定义多种效果!
  • SimpleCursorAdapter:用于显示简单文本类型的listView,一般在数据库那里会用到,不过有点过时, 不推荐使用!

https://www.jianshu.com/p/97994c9693f9

Android图片加载库

各个主流图片加载库的Github地址

UniversalImageLoader

Picasso

Glide

Fresco

图片加载库- 对比img

Glide功能列表

https://www.jianshu.com/p/c3a5518b58b2

img

  • 图片的异步加载(基础功能)
ImageView targetImageView = (ImageView) findViewById(R.id.ImageView);
        String Url = "http://218.192.170.132/1.jpg";

//Glide使用了流式接口的调用方式
//Glide类是核心实现类。
        Glide.with(context).load(Url).into(targetImageView);

//实现图片加载功能至少需要三个参数:
//with(Context context)
//Context对于很多Android API的调用都是必须的,这里就不多说了

//load(String imageUrl):被加载图像的Url地址。
//大多情况下,一个字符串代表一个网络图片的URL。
                
//into(ImageView targetImageView):图片最终要展示的地方。
                
  • 设置加载尺寸
Glide.with(this).load(imageUrl).override(800, 800).into(imageView);
  • 设置加载中以及加载失败图片
    api里面对placeholder()、error()函数中有多态实现,用的时候可以具体的熟悉一下
Glide
 .with(this)
  .load(imageUrl)
 .placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).into(imageView);
  • 设置加载动画
Glide.with(this).load(imageUrl).animate(R.anim.item_alpha_in).into(imageView);
final String url = mData.get(position);
Glide.with(mContext)
        .load(url)//图片地址
        .override(screenWidth, screenWidth)//大小
        .placeholder(R.mipmap.ic_launcher)//图片加载中时显示图片
        .into(holder.mImageView);//将图片加载到哪个ImageView中
  • 设置要加载的内容
    项目中有很多需要先下载图片然后再做一些合成的功能,比如项目中出现的图文混排,该如何实现目标下
Glide.with(this).load(imageUrl).centerCrop().into(new SimpleTarget<GlideDrawable>() {
            @Override
            public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
                imageView.setImageDrawable(resource);
            }
        });

Glide在bulide.gradle(app)中配置

repositories {
    mavenCentral()
    maven { url 'https://maven.google.com' }
}

dependencies {
    compile 'com.github.bumptech.glide:glide:4.11.0'
    // Skip this if you don't want to use integration libraries or configure Glide.
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}

另外,Glide中需要用到网络功能,因此你还得在AndroidManifest.xml中声明一下网络权限才行:

<uses-permission android:name="android.permission.INTERNET" />

Glide的详解

https://zhuanlan.zhihu.com/p/91948083 基本用法

https://zhuanlan.zhihu.com/p/92024248 从源码的角度理解Glide的执行流程中篇

https://zhuanlan.zhihu.com/p/92024955 从源码的角度理解Glide的执行流程下篇

https://zhuanlan.zhihu.com/p/92025775 深入探究Glide的缓存机制

https://zhuanlan.zhihu.com/p/92025942 玩转Glide的回调与监听

https://zhuanlan.zhihu.com/p/92026158 Glide强大的图片变换功能

https://zhuanlan.zhihu.com/p/92027112 探究Glide的自定义模块功能

https://zhuanlan.zhihu.com/p/92027343 实现带进度的Glide图片加载功能

https://zhuanlan.zhihu.com/p/92027663 带你全面了解Glide 4的用法

Glide压缩

使用Glide在绝大多数情况下我们都是不需要指定图片大小的。

我们平时在加载图片的时候很容易会造成内存浪费。什么叫内存浪费呢?比如说一张图片的尺寸是1000*1000像素,但是我们界面上的ImageView可能只有200*200像素,这个时候如果你不对图片进行任何压缩就直接读取到内存中,这就属于内存浪费了,因为程序中根本就用不到这么高像素的图片。

关于图片压缩这方参考文章:https://guolin.blog.csdn.net/article/details/9316683

而使用Glide,我们就完全不用担心图片内存浪费,甚至是内存溢出的问题。因为Glide从来都不会直接将图片的完整尺寸全部加载到内存中,而是用多少加载多少。Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支。在绝大多数情况下我们都是不需要指定图片大小的,因为Glide会自动根据ImageView的大小来决定图片的大小。

若给图片指定一个固定的大小,修改Glide加载部分的代码,如下所示:

Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .error(R.drawable.error)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .override(100, 100)//大小
     .into(imageView);

使用override()方法指定了一个图片的尺寸,也就是说,Glide现在只会将图片加载成100*100像素的尺寸,而不会管你的ImageView的大小是多少(ImageView属性要设为warp_content,若设为match_parent则图片大小按照ImageView来但像素依旧为100*100)。

Glide加载圆形和圆角图片

加载圆形图片:

RequestOptions mRequestOptions = RequestOptions.circleCropTransform()
.diskCacheStrategy(DiskCacheStrategy.NONE)//不做磁盘缓存
.skipMemoryCache(true);//不做内存缓存
Glide.with(mContext).load(userInfo.getImage()).apply(mRequestOptions).into(mUserIcon);

img

加载圆角图片:

//设置图片圆角角度
RoundedCorners roundedCorners= new RoundedCorners(6);
//通过RequestOptions扩展功能,override:采样率,因为ImageView就这么大,可以压缩图片,降低内存消耗
RequestOptions options=RequestOptions.bitmapTransform(roundedCorners).override(300, 300);
Glide.with(context).load(files.getFilePath()).apply(options).into(mUserPhoto);

img

设置圆角没有作用,查看是否设置了ScaleType属性

Adapter和Activity之间传值

Adapter:

private OnButtonClickListener mL;

public SelectPhotosAdapter(Context context, SelectPhotosActivity.myCallBack myCallBack,OnButtonClickListener listener) {
    this.context = context;
    this.myCallBack = myCallBack;
    this.mL=listener;
    screenWidth = (screenWidth - Utils.dp2px(context, 20)) / 3;
}

Button btn = (Button)convertView.findViewById(R.id.button);
try {
public View getView(final int position, View convertView, ViewGroup parent) {
	btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(mL != null){
                        mL.onButtonClick(path_list);
                    }
                }
            });
            } catch (Exception e) {
            e.printStackTrace();
        }
 }


 public interface OnButtonClickListener{
        void onButtonClick(List<String> pathList);
 }

Activity:

private void initData() {
    try {
        //回调函数:用于选中照片之后更新下面的展示
        //所有图片的适配器
        SelectPhotosAdapter adapter = new SelectPhotosAdapter(this, new myCallBack() {
                @SuppressLint("SetTextI18n")
                @Override
                public void updateList(final List<String> pathList) {//回调函数:用于选中照片之后更新下面的展示
                    recycleAdapter.setList(pathList);
                    selectphotos_number.setText(pathList.size() + "");
                    Gson gson2= new Gson();
                    String str2 = gson2.toJson(pathList);
                    selectphotos_determine.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Gson gson= new Gson();
                            String str = gson.toJson(pathList);
                            Intent intent = new Intent(SelectPhotosActivity.this, ShowActivity.class);
                            intent.putExtra("json_m",str);
                            startActivity(intent);
                        }
                    });
                }
            },new SelectPhotosAdapter.OnButtonClickListener(){

                @Override
                public void onButtonClick(List<String> pathList) {
                     Gson gson= new Gson();
                     String str = gson.toJson(pathList);
                     Intent intent = new Intent(SelectPhotosActivity.this, ShowActivity.class);
                     intent.putExtra("json_m",str);
                     startActivity(intent);
                }
            });
        recycleAdapter = new PhotosRecycleAdapter(this, new myCallBack() {
            @SuppressLint("SetTextI18n")
            @Override
            public void updateList(List<String> pathList) {//回调函数:用于选中照片之后更新下面的展示
                recycleAdapter.setList(pathList);
                selectphotos_number.setText(pathList.size() + "");
            }
        });
        selectphotos_rl.setAdapter(recycleAdapter);

        selectphotos_gv.setAdapter(adapter);
        adapter.setJsonArray(jsonArray);
        adapter.notifyDataSetChanged();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

Adapter中使用Intent跳转到另一个activity

Activity:

public class MainViewAdapter extends BaseAdapter {
    private Context mContext;
    private LayoutInflater bsman = null;     *// 初始化你的Context*
        public MainViewAdapter(Context context) { 
        this.mContext = context;
        bsman = LayoutInflater.from(context);
    } ........  
    

Adapter:

@Override 
public View getView(int position, View convertView, ViewGroup parent) {
        *// TODO Auto-generated method stub*
        convertView = bsman.inflate(R.layout.listviewitem, null);
        mButton=(Button)convertView.findViewById(R.id.button);
        mButton.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {    *// TODO Auto-generated method stub*
                Intent intent=new Intent(mContext,Bingsman.class);*//你要跳转的界面*    
                mContext.startActivity(intent);
            }
        });
       return convertView;
    }
}

json格式和list的转换

先导入Gson.jar

list转换为json

Gson gson = new Gson();  
List<Person> persons = new ArrayList<Person>();  
String str = gson.toJson(persons); 123

json转换为list

Gson gson = new Gson();  
List<Person> persons = gson.fromJson(str, new TypeToken<List<Person>>(){}.getType());

详解android:scaleType属性

android:scaleType是控制图片如何resized/moved来匹对ImageView的size。

ImageView.ScaleType / android:scaleType值的意义区别:

  • center 按图片的原来size居中显示,当图片长/宽超过View的长/宽,则截取图片的居中部分显示
  • centerCrop 按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽)
  • centerInside 将图片的内容完整居中显示,通过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽
  • fitCenter 把图片按比例扩大/缩小到View的宽度,居中显示
  • fitEnd 把图片按比例扩大/缩小到View的宽度,显示在View的下部分位置
  • fitStart 把图片按比例扩大/缩小到View的宽度,显示在View的上部分位置
  • fitXY 把图片不按比例扩大/缩小到View的大小显示
  • matrix 用矩阵来绘制,动态缩小放大图片来显示。

要注意一点,Drawable文件夹里面的图片命名是不能大写的

Fresco

fresco官方文档

编辑 build.gradle(app) 文件:

dependencies {
	compile 'com.facebook.fresco:fresco:0.12.0'
} 

下面的依赖需要根据需求添加:

dependencies {
	// 在 API < 14 上的机器支持 WebP 时,需要添加
	compile 'com.facebook.fresco:animated-base-support:0.12.0'
	// 支持 GIF 动图,需要添加
	compile 'com.facebook.fresco:animated-gif:0.12.0'
	// 支持 WebP (静态图+动图),需要添加
	compile 'com.facebook.fresco:animated-webp:0.12.0'
	compile 'com.facebook.fresco:webpsupport:0.12.0'
	// 仅支持 WebP 静态图,需要添加  compile 
	'com.facebook.fresco:webpsupport:0.12.0'
}

如果你仅仅是想简单下载一张网络图片,在下载完成之前,显示一张占位图,那么简单使用 SimpleDraweeView 即可。

在加载图片之前,你必须初始化Fresco类。你只需要调用Fresco.initialize一次即可完成初始化,在 Application 里面做这件事再适合不过了(如下面的代码),注意多次的调用初始化是无意义的。

[MyApplication.java]
public class MyApplication extends Application { 
@Override
	public void onCreate() {
    	super.onCreate(); 
        Fresco.initialize(this);
    }
} 

做完上面的工作后,你需要在 AndroidManifest.xml 中指定你的 Application 类。为了下载网络图片,请确认你声明了网络请求的权限。

<manifest
	...    >
	<uses-permission android:name="android.permission.INTERNET" />
	<application 
    	... 
   		android:label="@string/app_name" 
        android:name=".MyApplication"
        > 
        ... 
        </application> 
        ...
</manifest> 

在xml布局文件中, 加入命名空间:

<!-- 其他元素-->
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"    
	xmlns:fresco="http://schemas.android.com/apk/res-auto"    
	android:layout_height="match_parent"    
	android:layout_width="match_parent"> 

加入SimpleDraweeView:

<com.facebook.drawee.view.SimpleDraweeView 
	android:id="@+id/my_image_view"
    android:layout_width="130dp"
    android:layout_height="130dp"    
    fresco:placeholderImage="@drawable/my_drawable" 
    fresco:fadeDuration="400"    //淡入时长
    fresco:progressBarImage="@anim/progress_round" //图片加载时显示的动画
    fresco:failureImage="@drawable/download_error"    //下载失败时的图片
    fresco:failureImageScaleType="fitXY"
    android:scaleType="fitXY"/> 

开始加载图片:

Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri); 

剩下的,Fresco会替你完成:

  • 显示占位图直到加载完成;
  • 下载图片;
  • 缓存图片;
  • 图片不再显示时,从内存中移除;

等等等等。

Fresco 支持许多URI格式。

特别注意:Fresco 不支持 相对路径的URI. 所有的 URI 都必须是绝对路径,并且带上该 URI 的 scheme。

如下:

类型SCHEME示例
远程图片http://, https://HttpURLConnection 或者参考 使用其他网络加载方案
本地文件file://FileInputStream
Content providercontent://ContentResolver
asset目录下的资源asset://AssetManager
res目录下的资源res://Resources.openRawResource
Uri中指定图片数据data:mime/type;base64,数据类型必须符合 rfc2397规定 (仅支持 UTF-8)

res 示例:

Uri uri = Uri.parse("res://包名(实际可以是任何字符串甚至留空)/" + R.drawable.ic_launcher); 

注意,只有图片资源才能使用在Image pipeline中,比如(PNG)。其他资源类型,比如字符串,或者XML Drawable在Image pipeline中没有意义。所以加载的资源不支持这些类型。

ShapeDrawable这样声明在XML中的drawable可能引起困惑。注意到这毕竟不是图片。如果想把这样的drawable作为图像显示,那么把这个drawable设置为占位图,然后把URI设置为null

Android 控件 RecyclerView

https://www.jianshu.com/p/4f9591291365

RecyclerView的优点

RecyclerView并不会完全替代ListView(这点从ListView没有被标记为@Deprecated可以看出),两者的使用场景不一样。但是RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView, 横向滚动的GridView, 瀑布流控件,因为RecyclerView能够实现所有这些功能。

比如:有一个需求是屏幕竖着的时候的显示形式是ListView,屏幕横着的时候的显示形式是2列的GridView,此时如果用RecyclerView,则通过设置LayoutManager一行代码实现替换。

RecylerView相对于ListView的优点罗列如下:

  • RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
    直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
  • 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
  • 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
    例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等)。也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。
  • 可设置Item的间隔样式(可绘制)
    通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。
  • 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。

但是关于Item的点击和长按事件,需要用户自己去实现。

引用

在build.gradle(app)文件中引入该类。

implementation 'androidx.recyclerview:recyclerview:1.1.0'

布局

Activity布局文件activity_rv.xml

Item的布局文件item_1.xml

创建适配器

标准实现步骤如下:
创建Adapter:创建一个继承RecyclerView.Adapter<VH>的Adapter类(VH是ViewHolder的类名)
创建ViewHolder:在Adapter中创建一个继承RecyclerView.ViewHolder的静态内部类,记为VH。ViewHolder的实现和ListView的ViewHolder实现几乎一样。
③ 在Adapter中实现3个方法

  • onCreateViewHolder()
    这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。

需要注意的是在onCreateViewHolder()中,映射Layout必须为

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);

而不能是:

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
  • onBindViewHolder()
    这个方法主要用于适配渲染数据到View中。方法提供给你了一viewHolder而不是原来的convertView。
  • getItemCount()
    这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目。

可以看出,RecyclerView将ListView中getView()的功能拆分成了onCreateViewHolder()onBindViewHolder()

基本的Adapter实现如下:

// ① 创建Adapter
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.VH>{
    //② 创建ViewHolder
    public static class VH extends RecyclerView.ViewHolder{
        public final TextView title;
        public VH(View v) {
            super(v);
            title = (TextView) v.findViewById(R.id.title);
        }
    }
    
    private List<String> mDatas;
    public NormalAdapter(List<String> data) {
        this.mDatas = data;
    }

    //③ 在Adapter中实现3个方法
    @Override
    public void onBindViewHolder(VH holder, int position) {
        holder.title.setText(mDatas.get(position));
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //item 点击事件
            }
        });
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        //LayoutInflater.from指定写法
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
        return new VH(v);
    }
}

设置RecyclerView

创建完Adapter,接着对RecyclerView进行设置,一般来说,需要为RecyclerView进行四大设置,也就是后文说的四大组成:

  • Layout Manager(必选)
  • Adapter(必选)
  • Item Decoration(可选,默认为空)
  • Item Animator(可选,默认为DefaultItemAnimator)

如果要实现ListView的效果,只需要设置Adapter和Layout Manager,如下:

List<String> data = initData();
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new NormalAdapter(data));

四大组成

RecyclerView的四大组成是:

  • Layout Manager:Item的布局。
  • Adapter:为Item提供数据。
  • Item Decoration:Item之间的Divider。
  • Item Animator:添加、删除Item动画。

Layout Manager布局管理器

在最开始就提到,RecyclerView 能够支持各种各样的布局效果,这是 ListView 所不具有的功能,那么这个功能如何实现的呢?其核心关键在于 RecyclerView.LayoutManager 类中。从前面的基础使用可以看到,RecyclerView 在使用过程中要比 ListView 多一个 setLayoutManager 步骤,这个 LayoutManager 就是用于控制我们 RecyclerView 最终的展示效果的。

LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。

RecyclerView提供了三种布局管理器

  • LinerLayoutManager 以垂直或者水平列表方式展示Item
  • GridLayoutManager 以网格方式展示Item
  • StaggeredGridLayoutManager 以瀑布流方式展示Item

如果你想用 RecyclerView 来实现自己自定义效果,则应该去继承实现自己的 LayoutManager,并重写相应的方法,而不应该想着去改写 RecyclerView。

LayoutManager 常见 API

关于 LayoutManager 的使用有下面一些常见的 API(有些在 LayoutManager 实现的子类中)

    canScrollHorizontally();//能否横向滚动
    canScrollVertically();//能否纵向滚动
    scrollToPosition(int position);//滚动到指定位置

    setOrientation(int orientation);//设置滚动的方向
    getOrientation();//获取滚动方向

    findViewByPosition(int position);//获取指定位置的Item View
    findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
    findFirstVisibleItemPosition();//获取第一个可见Item的位置
    findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
    findLastVisibleItemPosition();//获取最后一个可见Item的位置

上面仅仅是列出一些常用的 API 而已,更多的 API 可以查看官方文档,通常你想用 RecyclerView 实现某种效果,例如指定滚动到某个 Item 位置,但是你在 RecyclerView 中又找不到可以调用的 API 时,就可以跑到 LayoutManager 的文档去看看,基本都在那里。
另外还有一点关于瀑布流布局效果 StaggeredGridLayoutManager 想说的,看到网上有些文章写的示例代码,在设置了 StaggeredGridLayoutManager 后仍要去 Adapter 中动态设置 View 的高度,才能实现瀑布流,这种做法是完全错误的,之所以 StaggeredGridLayoutManager 的瀑布流效果出不来,基本是 item 布局的 xml 问题以及数据问题导致。如果要在 Adapter 中设置 View 的高度,则完全违背了 LayoutManager 的设计理念了。

LinearLayoutManager源码分析

这里我们简单分析LinearLayoutManager的实现。

对于LinearLayoutManager来说,比较重要的几个方法有:

  • onLayoutChildren(): 对RecyclerView进行布局的入口方法。
  • fill(): 负责填充RecyclerView。
  • scrollVerticallyBy():根据手指的移动滑动一定距离,并调用fill()填充。
  • canScrollVertically()canScrollHorizontally(): 判断是否支持纵向滑动或横向滑动。

onLayoutChildren()的核心实现如下:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    detachAndScrapAttachedViews(recycler); //将原来所有的Item View全部放到Recycler的Scrap Heap或Recycle Pool
    fill(recycler, mLayoutState, state, false); //填充现在所有的Item View
}

RecyclerView的回收机制有个重要的概念,即将回收站分为Scrap Heap和Recycle Pool,其中Scrap Heap的元素可以被直接复用,而不需要调用onBindViewHolder()detachAndScrapAttachedViews()会根据情况,将原来的Item View放入Scrap Heap或Recycle Pool,从而在复用时提升效率。

fill()是对剩余空间不断地调用layoutChunk(),直到填充完为止。layoutChunk()的核心实现如下:

public void layoutChunk() {
    View view = layoutState.next(recycler); //调用了getViewForPosition()
    addView(view);  //加入View
    measureChildWithMargins(view, 0, 0); //计算View的大小
    layoutDecoratedWithMargins(view, left, top, right, bottom); //布局View
}

其中next()调用了getViewForPosition(currentPosition),该方法是从RecyclerView的回收机制实现类Recycler中获取合适的View,在后文的回收机制中会介绍该方法的具体实现。

Android获得SD卡权限

加两行代码在MainActivity.xml中

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

https://blog.csdn.net/chengyingzhilian/article/details/7279494

很详细,主要就是位置问题

代码中动态申请权限

 private void requestMyPermissions() {
       
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //没有授权,编写申请权限代码
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
        } else {
            Log.d(TAG, "requestMyPermissions: 有写SD权限");
        }
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //没有授权,编写申请权限代码
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
        } else {
            Log.d(TAG, "requestMyPermissions: 有读SD权限");
        }
    }

相册

public class MainActivity extends AppCompatActivity {
    //调用系统相册-选择图片
    private static final int IMAGE = 1;
    //所需权限
	//<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void onClick(View v) {
        //调用相册
        Intent intent = new Intent(Intent.ACTION_PICK,
                android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        startActivityForResult(intent, IMAGE);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //获取图片路径
        if (requestCode == IMAGE && resultCode == Activity.RESULT_OK && data != null) {
            Uri selectedImage = data.getData();
            String[] filePathColumns = {MediaStore.Images.Media.DATA};
            Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null);
            c.moveToFirst();
            int columnIndex = c.getColumnIndex(filePathColumns[0]);
            String imagePath = c.getString(columnIndex);
            showImage(imagePath);
            c.close();
        }
    }
    //加载图片
    private void showImage(String imaePath){
        Bitmap bm = BitmapFactory.decodeFile(imaePath);
		((ImageView)findViewById(R.id.image)).setImageBitmap(bm);
    }
}

相机

@OnClick(R.id.to_camear_btn)
    public void onClick() {
//        checkSelfPermission 检测有没有 权限
//        PackageManager.PERMISSION_GRANTED 有权限
//        PackageManager.PERMISSION_DENIED  拒绝权限
        if(ActivityCompat.checkSelfPermission(this,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            //权限发生了改变 true  //  false 小米
            if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)){
                new AlertDialog.Builder(this).setTitle("title")
                        .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // 请求授权
                                ActivityCompat.requestPermissions(PermissionActivity.this,new String[]{Manifest.permission.CAMERA},1);
                            }
                        }).setNegativeButton("cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                }).create().show();
            }else {
                ActivityCompat.requestPermissions(PermissionActivity.this,new String[]{Manifest.permission.CAMERA},1);
            }
        }else{
            camear();
        }
    }

    /**
     *
     * @param requestCode
     * @param permissions 请求的权限
     * @param grantResults 请求权限返回的结果
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == 1){
            // camear 权限回调
            if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                // 表示用户授权
                Toast.makeText(this, " user Permission" , Toast.LENGTH_SHORT).show();
                camear();
            } else {
                //用户拒绝权限
                Toast.makeText(this, " no Permission" , Toast.LENGTH_SHORT).show();
            }
        }
    }

    public void camear(){
        try {
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            startActivityForResult(intent,1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {            
                @Override
                public void onClick(View view) {
                    onTakePhoto();
                }
            });
        }
        public void onTakePhoto()   {
            if (Build.VERSION.SDK_INT>=23)       {
                int request= ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
                if (request!= PackageManager.PERMISSION_GRANTED)//缺少权限,进行权限申请
                {
                    ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},123);
                    return;//
                }
                else
                {
                    //权限同意,不需要处理,去掉用拍照的方法               
                    // Toast.makeText(this,"权限同意",Toast.LENGTH_SHORT).show();
                }
            }
            else{
                //低于23 不需要特殊处理,去掉用拍照的方法
            }
        }
        //参数 requestCode是我们在申请权限的时候使用的唯一的申请码 
        //String[] permission则是权限列表,一般用不到
        //int[] grantResults 是用户的操作响应,包含这权限是够请求成功
        //由于在权限申请的时候,我们就申请了一个权限,所以此处的数组的长度都是1
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode==123)
            {            //当然权限多了,建议使用Switch,不必纠结于此
                if (grantResults[0]==PackageManager.PERMISSION_GRANTED)            {
                    Toast.makeText(this, "权限申请成功",Toast.LENGTH_SHORT).show();
                }else if (grantResults[0]== PackageManager.PERMISSION_DENIED)            {
                    Toast.makeText(this, "权限申请失败,用户拒绝权限", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

解决Android10读取不到/sdcard/、/storage/emulated/0/文件的问题

Android Q官方文档

Android Q 分区存储

  1. Android Q文件存储机制修改成了沙盒模式,和IOS神似
  2. 应用只能访问自己沙盒下的文件和公共媒体文件
  3. 对于Android Q以下,还是使用老的文件存储方式

上传头像,涉及到本地上传,或者拍照上传,10以下的系统可以正常运行,Android10,操作拍照,或者上传,报错,打日志,获取相片的路径为空

解决办法:
在AndroidManifest.xml 里的application中添加 android:requestLegacyExternalStorage=“true” 临时解决。可以兼容到Android 11。或者targetsdk 降低到29以下。

<application  
     android:requestLegacyExternalStorage="true"
>

https://developer.android.google.cn/about/versions/11/privacy Android 11 中的隐私权及其变更

content://com.caminar.usblive.provider/external_storage_root/SBCamera_update_Bob3.apk

handler作用:

传递消息Message

//2种创建消息方法
//1.通过handler实例获取
Handler handler = new Handler();
Message message=handler.obtainMessage();

//2.通过Message获取
Message message=Message.obtain();

//源码中第一种获取方式其实也是内部调用了第二种:
public final Message obtainMessage(){
    return Message.obtain(this);
}

不建议直接new Message,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存的。

//传递的数据
Bundle bundle = new Bundle();
bundle.putString("msg", "传递我这个消息");
//发送数据
Message message = Message.obtain();
message.setData(bundle);   //message.obj=bundle  传值也行
message.what = 0x11;
handler.sendMessage(message);

//数据的接收
final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0x11) {
                Bundle bundle = msg.getData();
                String date = bundle.getString("msg");
            }
        }
};

子线程通知主线程更新ui

        //创建handler
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x11) {
                    //更新ui
                          ......
                }
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                //FIXME 这里直接更新ui是不行的
                //还有其他更新ui方式,runOnUiThread()等          
                message.what = 0x11;     
                handler.sendMessage(message);  
            }
        }).start();

常用api

    //消息
    Message message = Message.obtain();
    //发送消息
        new Handler().sendMessage(message);
    //延时1s发送消息
        new Handler().sendMessageDelayed(message, 1000);
    //发送带标记的消息(内部创建了message,并设置msg.what = 0x1)
        new Handler().sendEmptyMessage(0x1);
    //延时1s发送带标记的消息
        new Handler().sendEmptyMessageDelayed(0x1, 1000);
    //延时1秒发送消息(第二个参数为:相对系统开机时间的绝对时间,而SystemClock.uptimeMillis()是当前开机时间)
        new Handler().sendMessageAtTime(message, SystemClock.uptimeMillis() + 1000);
    //避免内存泄露的方法:
    //移除标记为0x1的消息
        new Handler().removeMessages(0x1);
    //移除回调的消息
        new Handler().removeCallbacks(Runnable);
    //移除回调和所有message
        new Handler().removeCallbacksAndMessages(null);
ad(new Runnable() {
            @Override
            public void run() {
                //FIXME 这里直接更新ui是不行的
                //还有其他更新ui方式,runOnUiThread()等          
                message.what = 0x11;     
                handler.sendMessage(message);  
            }
        }).start();

常用api

    //消息
    Message message = Message.obtain();
    //发送消息
        new Handler().sendMessage(message);
    //延时1s发送消息
        new Handler().sendMessageDelayed(message, 1000);
    //发送带标记的消息(内部创建了message,并设置msg.what = 0x1)
        new Handler().sendEmptyMessage(0x1);
    //延时1s发送带标记的消息
        new Handler().sendEmptyMessageDelayed(0x1, 1000);
    //延时1秒发送消息(第二个参数为:相对系统开机时间的绝对时间,而SystemClock.uptimeMillis()是当前开机时间)
        new Handler().sendMessageAtTime(message, SystemClock.uptimeMillis() + 1000);
    //避免内存泄露的方法:
    //移除标记为0x1的消息
        new Handler().removeMessages(0x1);
    //移除回调的消息
        new Handler().removeCallbacks(Runnable);
    //移除回调和所有message
        new Handler().removeCallbacksAndMessages(null);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值