Android基础知识-3

Canvas的本质

  • 绘制内容是根据画布(Canvas)的规定 绘制在屏幕 上的
  • 画布(Canvas)只是绘制时的规则,但 内容实际上是绘制在屏幕上的
    为了更好地说明绘制内容的本质和Canvas,请看下面例子:

实例

  • 实例情况:先画一个矩形(蓝色);然后移动画布;再画一个矩形(红色)
  • 代码分析:
        // 画一个矩形(蓝色)
       canvas.drawRect(100, 100, 150, 150, mPaint1);

       // 将画布的原点移动到(400,500)
       canvas.translate(400,500);

       // 再画一个矩形(红色)
       canvas.drawRect(100, 100, 150, 150, mPaint2);
image
  • 具体流程分析:


    image
  • 总结
  1. 绘制内容是根据画布的规定绘制在屏幕上的内容实际上是绘制在屏幕上;
  2. 画布,即Canvas,只是规定了绘制内容时的规则;
  3. 内容的位置由坐标决定,而坐标是相对于画布而言的

基础

Paint类

  • 定义:画笔

  • 作用:确定绘制内容的具体效果(如颜色、大小等等)

  • 具体使用:

步骤1:创建一个画笔对象
步骤2:画笔设置,即设置绘制内容的具体效果(如颜色、大小等等)
步骤3:初始化画笔(尽量选择在View的构造函数)

// 步骤1:创建一个画笔
private Paint mPaint = new Paint();

// 步骤2:初始化画笔
// 根据需求设置画笔的各种属性,具体如下:

   private void initPaint() {

       // 设置最基本的属性
       // 设置画笔颜色
       // 可直接引入Color类,如Color.red等
       mPaint.setColor(int color); 
       // 设置画笔模式
        mPaint.setStyle(Style style); 
       // Style有3种类型:
       // 类型1:Paint.Style.FILLANDSTROKE(描边+填充)
       // 类型2:Paint.Style.FILL(只填充不描边)
       // 类型3:Paint.Style.STROKE(只描边不填充)
       // 具体差别请看下图:
       // 特别注意:前两种就相差一条边
       // 若边细是看不出分别的;边粗就相当于加粗       
       
       //设置画笔的粗细
       mPaint.setStrokeWidth(float width)       
       // 如设置画笔宽度为10px
       mPaint.setStrokeWidth(10f);    

       // 不常设置的属性
       // 得到画笔的颜色     
       mPaint.getColor()      
       // 设置Shader
       // 即着色器,定义了图形的着色、外观
       // 可以绘制出多彩的图形
       // 具体请参考文章:http://blog.csdn.net/iispring/article/details/50500106
       Paint.setShader(Shader shader)  

       //设置画笔的a,r,p,g值
      mPaint.setARGB(int a, int r, int g, int b)      
        //设置透明度
       mPaint.setAlpha(int a)   
      //得到画笔的Alpha值
       mPaint.getAlpha()        


       // 对字体进行设置(大小、颜色)
       //设置字体大小
         mPaint.setTextSize(float textSize)       

       // 文字Style三种模式:
         mPaint.setStyle(Style style); 
       // 类型1:Paint.Style.FILLANDSTROKE(描边+填充)
       // 类型2:Paint.Style.FILL(只填充不描边)
       // 类型3:Paint.Style.STROKE(只描边不填充) 
       
     // 设置对齐方式   
     setTextAlign()
     // LEFT:左对齐
     // CENTER:居中对齐
     // RIGHT:右对齐

       //设置文本的下划线
         setUnderlineText(boolean underlineText)      
       
       //设置文本的删除线
       setStrikeThruText(boolean strikeThruText)    

        //设置文本粗体
       setFakeBoldText(boolean fakeBoldText)  
       
          // 设置斜体
       Paint.setTextSkewX(-0.5f);


       // 设置文字阴影
       Paint.setShadowLayer(5,5,5,Color.YELLOW);
    }

// 步骤3:在构造函数中初始化
   public CarsonView(Context context, AttributeSet attrs) {
       super(context, attrs);
       initPaint();
   }

Canvas 的使用

对象创建& 获取

Canvas对象&获取的方法有4个:

// 方法1
// 利用空构造方法直接创建对象
Canvas canvas = new Canvas();

// 方法2
// 通过传入装载画布Bitmap对象创建Canvas对象
// CBitmap上存储所有绘制在Canvas的信息
Canvas canvas = new Canvas(bitmap)

// 方法3
// 通过重写View.onDraw()创建Canvas对象
// 在该方法里可以获得这个View对应的Canvas对象

  @Override
   protected void onDraw(Canvas canvas) {
       super.onDraw(canvas);
       //在这里获取Canvas对象
   }

// 方法4
// 在SurfaceView里画图时创建Canvas对象

       SurfaceView surfaceView = new SurfaceView(this);
       // 从SurfaceView的surfaceHolder里锁定获取Canvas
       SurfaceHolder surfaceHolder = surfaceView.getHolder();
       //获取Canvas
       Canvas c = surfaceHolder.lockCanvas();
       
       // ...(进行Canvas操作)
       // Canvas操作结束之后解锁并执行Canvas
       surfaceHolder.unlockCanvasAndPost(c);

官方推荐方法4来创建并获取Canvas,原因:

  • SurfaceView里有一条线程是专门用于画图,所以方法4的画图性能最好,并适用于高质量的、刷新频率高的图形
  • 而方法3刷新频率低于方法3,但系统花销小,节省资源

绘制方法使用

  • 利用Canvas类可绘画出很多内容,如图形、文字、线条等等;
  • 对应使用的方法如下:


    image

特别注意
Canvas具体使用时是在复写的 onDraw() 里:

 @Override
   protected void onDraw(Canvas canvas){
     
       super.onDraw(canvas);
  
   // 对Canvas进行一系列设置
   //  如画圆、画直线等等
  canvas.drawColor(Color.BLUE); 
   // ...
   }
}

绘制颜色

  • 作用:将颜色填充整个画布,常用于绘制底色
  • 具体使用
   // 传入一个Color类的常量参数来设置画布颜色
   // 绘制蓝色
  canvas.drawColor(Color.BLUE); 
image

绘制基本图形

a. 绘制点(drawPoint)

  • 原理:在某个坐标处绘制点
  • 具体使用:
// 特别注意:需要用到画笔Paint
// 所以之前记得创建画笔
// 为了区分,这里使用了两个不同颜色的画笔
// 描绘一个点
// 在坐标(200,200)处
canvas.drawPoint(300, 300, mPaint1);    

// 绘制一组点,坐标位置由float数组指定
// 此处画了3个点,位置分别是:(600,500)、(600,600)、(600,700)
canvas.drawPoints(new float[]{         
               600,500,
               600,600,
               600,700
       },mPaint2);

image

绘制直线(drawLine)

  • 原理:两点(初始点 & 结束点)确定一条直线
  • 具体使用:
// 画一条直线
// 在坐标(100,200),(700,200)之间绘制一条直线
  canvas.drawLine(100,200,700,200,mPaint1);

// 绘制一组线
// 在坐标(400,500),(500,500)之间绘制直线1
// 在坐标(400,600),(500,600)之间绘制直线2
       canvas.drawLines(new float[]{
               400,500,500,500,
               400,600,500,600
       },mPaint2);
   }

image

绘制矩形(drawRect)

  • 原理:矩形的对角线顶点确定一个矩形

一般是采用左上角和右下角的两个点的坐标。

  • 具体使用
// 关于绘制矩形,Canvas提供了三种重载方法

      // 方法1:直接传入两个顶点的坐标
      // 两个顶点坐标分别是:(100,100),(800,400)
       canvas.drawRect(100,100,800,400,mPaint);

       // 方法2:将两个顶点坐标封装为RectRectF
       Rect rect = new Rect(100,100,800,400);
       canvas.drawRect(rect,mPaint);

       // 方法3:将两个顶点坐标封装为RectF
       RectF rectF = new RectF(100,100,800,400);
       canvas.drawRect(rectF,mPaint);

       // 特别注意:Rect类和RectF类的区别
       // 精度不同:Rect = int & RectF = float

       // 三种方法画出来的效果是一样的。

image

绘制圆角矩形

  • 原理:矩形的对角线顶点确定一个矩形

类似于绘制矩形

  • 具体使用
      // 方法1:直接传入两个顶点的坐标
      // API21时才可使用
      // 第5、6个参数:rx、ry是圆角的参数,下面会详细描述
      canvas.drawRoundRect(100,100,800,400,30,30,mPaint);
     
       // 方法2:使用RectF类
       RectF rectF = new RectF(100,100,800,400);
       canvas.drawRoundRect(rectF,30,30,mPaint);

![s

                                                                  ](https://upload-images.jianshu.io/upload_images/25159888-9c21fe0ff64e6f4f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 与矩形相比,圆角矩形多了两个参数rx 和 ry
  • 圆角矩形的角是椭圆的圆弧,rx 和 ry实际上是椭圆的两个半径,如下图:


    image
  • 特别注意:当 rx大于宽度的一半, ry大于高度一半 时,画出来的为椭圆

实际上,在rx为宽度的一半,ry为高度的一半时,刚好是一个椭圆;但由于当rx大于宽度一半,ry大于高度一半时,无法计算出圆弧,所以drawRoundRect对大于该数值的参数进行了修正,凡是大于一半的参数均按照一半来处理

绘制椭圆

  • 原理:矩形的对角线顶点确定矩形,根据传入矩形的长宽作为长轴和短轴画椭圆

椭圆传入的参数和矩形是一样的;
绘制椭圆实际上是绘制一个矩形的内切图形。

  • 具体使用
       // 方法1:使用RectF类
       RectF rectF = new RectF(100,100,800,400);
       canvas.drawOval(rectF,mPaint);

       // 方法2:直接传入与矩形相关的参数
       canvas.drawOval(100,100,800,400,mPaint);

       // 为了方便表示,画一个和椭圆一样参数的矩形
        canvas.drawRect(100,100,800,400,mPaint);
image

绘制圆

  • 原理:圆心坐标+半径决定圆
  • 具体使用
// 参数说明:
// 1、2:圆心坐标
// 3:半径
// 4:画笔

// 绘制一个圆心坐标在(500,500),半径为400 的圆。
   canvas.drawCircle(500,500,400,mPaint);  
image

绘制圆弧

  • 原理:通过圆弧角度的起始位置和扫过的角度确定圆弧
  • 具体使用
// 绘制圆弧共有两个方法
// 相比于绘制椭圆,绘制圆弧多了三个参数:
startAngle  // 确定角度的起始位置
sweepAngle // 确定扫过的角度
useCenter   // 是否使用中心(下面会详细说明)

// 方法1
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}

// 方法2
public void drawArc(float left, float top, float right, float bottom, float startAngle,
           float sweepAngle, boolean useCenter, @NonNull Paint paint) {}

为了理解第三个参数: useCenter ,看以下示例:

// 以下示例:绘制两个起始角度为0度、扫过90度的圆弧
// 两者的唯一区别就是是否使用了中心点

   // 绘制圆弧1(无使用中心)
       RectF rectF = new RectF(100, 100, 800,400);
       // 绘制背景矩形
       canvas.drawRect(rectF, mPaint1);
       // 绘制圆弧
       canvas.drawArc(rectF, 0, 90, false, mPaint2);

  // 绘制圆弧2(使用中心)
       RectF rectF2 = new RectF(100,600,800,900);
       // 绘制背景矩形
       canvas.drawRect(rectF2, mPaint1);
       // 绘制圆弧
       canvas.drawArc(rectF2,0,90,true,mPaint2);
image

从示例可以发现:

  • 不使用中心点:圆弧的形状 = (起、止点连线+圆弧)构成的面积
  • 使用中心店:圆弧面积 = (起点、圆心连线 + 止点、圆心连线+圆弧)构成的面积

绘制文字

绘制文字分为三种场景:

  • 情况1:指定文本开始的位置

即指定文本基线位置
基线x默认在字符串左侧,基线y默认在字符串下方

  • 情况2:指定每个文字的位置
  • 情况3:指定路径,并根据路径绘制文字

文字的样式(大小,颜色,字体等)具体由画笔Paint控制
情况1:指定文本开始的位置

// 参数text:要绘制的文本
// 参数x,y:指定文本开始的位置(坐标)

// 参数paint:设置的画笔属性
   public void drawText (String text, float x, float y, Paint paint)

// 实例
canvas.drawText("abcdefg",300,400,mPaint1);



// 仅绘制文本的一部分
// 参数start,end:指定绘制文本的位置
// 位置以下标标识,由0开始
   public void drawText (String text, int start, int end, float x, float y, Paint paint)
   public void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)

// 对于字符数组char[]
// 截取文本使用起始位置(index)和长度(count)
   public void drawText (char[] text, int index, int count, float x, float y, Paint paint)

// 实例:绘制从位置1-3的文本
canvas.drawText("abcdefg",1,4,300,400,mPaint1);

       // 字符数组情况
       // 字符数组(要绘制的内容)
       char[] chars = "abcdefg".toCharArray();

       // 参数为 (字符数组 起始坐标 截取长度 基线x 基线y 画笔)
       canvas.drawText(chars,1,3,200,500,textPaint);
       // 效果同上
image

情况2:分别指定文本的位置

// 参数text:绘制的文本
// 参数pos:数组类型,存放每个字符的位置(坐标)
// 注意:必须指定所有字符位置
public void drawPosText (String text, float[] pos, Paint paint)

// 对于字符数组char[],可以截取部分文本进行绘制
// 截取文本使用起始位置(index)和长度(count)
   public void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)

// 特别注意:
// 1. 在字符数量较多时,使用会导致卡顿
// 2. 不支持emoji等特殊字符,不支持字形组合与分解

 // 实例
 canvas.drawPosText("abcde", new float[]{
               100, 100,    // 第一个字符位置
               200, 200,    // 第二个字符位置
               300, 300,    // ...
               400, 400,
               500, 500
       }, mPaint1);




// 数组情况(绘制部分文本)
      char[] chars = "abcdefg".toCharArray();

       canvas.drawPosText(chars, 1, 3, new float[]{
               300, 300,    // 指定的第一个字符位置
               400, 400,    // 指定的第二个字符位置
               500, 500,    // 指定的第三个字符位置

       }, mPaint1);
image

情况3:指定路径,并根据路径绘制文字

      
// 在路径(540,750,640,450,840,600)写上"在Path上写的字:Carson_Ho"字样
       // 1.创建路径对象
       Path path = new Path();
       // 2. 设置路径轨迹
       path.cubicTo(540, 750, 640, 450, 840, 600);
        // 3. 画路径
       canvas.drawPath(path,mPaint2);
       // 4. 画出在路径上的字
       canvas.drawTextOnPath("在Path上写的字:Carson_Ho", path, 50, 0, mPaint2);
image

其他请参考:自定义View Canvas类使用教程

数据存储于IO

持久化技术简介

数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的。持久化技术提供了一种机制,可以让数据在瞬时状态和持久状态之间进行转换。

本章节主要关注:文件存储、SharedPreferences存储以及数据库存储。

File 存储

文件存储是Android最基本的数据存储方式,它不对存储的内容进行任何格式化处理,所有数据都是原封不懂得保存到文件当中,因而比较适合存储一些简单的文本数据或二进制数据。如果想使用此方式保存一些较为复杂的结构化数据,就需要定义一套自己的格式规范,方便之后将数据从文件中重新解析出来。

将数据存储到文件中

Context 类中提供了一个 openFileOutput() 方法,可以用于将数据存储到指定的文件中。这个方法接收两个参数:第一个参数是文件名,在文件创建的时候使用,注意这里指定的文件名不可以包含路径,因为所有的文件都默认存储到 /data/data//files/ 目录下;第二个参数是文件的操作模式,主要有 MODE_PRIVATEMODE_APPEND 两种模式可选,默认是 MODE_PRIVATE表示当指定相同文件名的时候,所写入的内容将会覆盖原文件中的内容,而 MODE_APPEND 则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。其实文件的操作模式本来还有另外两种: MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 。这两种模式表示允许其他应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危险,很容易引起应用的安全漏洞,已在Android 4.2版本中被废弃。

openFileOutput() 方法返回的是一个FileOutputStream 对象,得到这个对象之后就可以使用Java流的方式将数据写入文件中了。以下是一段简单的代码示例,展示了如何将一段文本内容保存到文件中:

   fun save(inputText: String) {
       try {
           val output = openFileOutput("data", Context.MODE_PRIVATE)
           val writer = BufferedWriter(OutputStreamWriter(output))
           writer.use {
               it.write(inputText)
           }
       } catch (e: IOException) {
           e.printStackTrace()
       }
   }

这里通过 openFileOutput() 方法能够得到一个 FileOutputStream 对象,然后借助它构建出一个 OutputStreamWriter 对象,接着再使用 OutputStreamWriter 构建出一个 BufferedWriter 对象,这样你就可以通过 BufferedWriter 将文本内容写入文件中了。

注意,这里还使用了一个 use 函数,这是 Kotlin 提供的一个内置扩展函数。它会保证在Lambda表达式中的代码全部执行完之后自动将外层的流关闭,这样就不需要我们再编写一个finally语句,手动去关闭流了,是一个非常好用的扩展函数。

首先创建一个 FilePersistenceTest 项目,并修改 activity_main.xml 中的代码,如下所示:




   

其实现在你就可以运行一下程序了,界面上肯定会有一个文本输入框。然后在文本输入框中随意输入点什么内容,再按下 Back 键,这时输入的内容肯定就已经丢失了,因为它只是瞬时数据,在 Activity 被销毁后就会被回收。而这里我们要做的,就是在数据被回收之前,将它存储到文件当中。修改 MainActivity 中的代码,如下所示:

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
   }
   override fun onDestroy() {
       super.onDestroy()
       val inputText = editText.text.toString()
       save(inputText)
   }
   private fun save(inputText: String) {
       try {
           val output = openFileOutput("data", Context.MODE_PRIVATE)
           val writer = BufferedWriter(OutputStreamWriter(output))
           writer.use {
               it.write(inputText)
           }
       } catch (e: IOException) {
           e.printStackTrace()
       }
   }
}

可以看到,首先我们重写了 onDestroy() 方法,这样就可以保证在 Activity 销毁之前一定会调用这个方法。在 onDestroy() 方法中,我们获取了 EditText 中输入的内容,并调用 save() 方法把输入的内容存储到文件中,文件命名为 datasave() 方法中的代码和之前的示例基本相同,这里就不再做解释了。现在重新运行一下程序,并在 EditText 中输入一些内容.

然后按下 Back 键关闭程序,这时我们输入的内容就保存到文件中了。那么如何才能证实数据确实已经保存成功了呢?我们可以借助 Device File Explorer 工具查看一下。这个工具在 Android Studio 的右侧边栏当中,通常是在右下角的位置,如果你的右侧边栏中没有这个工具的话,也可以使用快捷键Ctrl + Shift + A(Mac系统是command + shift + A)打开搜索功能,在搜索框中输入“Device File Explorer”即可找到这个工具。
这个工具其实就相当于一个设备文件浏览器,我们在这里找到 /data/data/com.example.filepersistencetest/files/ 目录,可以看到,现在已经生成了一个data文件,

从文件中读取数据

类似于将数据存储到文件中, Context 类中还提供了一个 openFileInput() 方法,用于从文件中读取数据。这个方法要比 openFileOutput() 简单一些,它只接收一个参数,即要读取的文件名,然后系统会自动到 /data/data//files/ 目录下加载这个文件,并返回一个 FileInputStream 对象,得到这个对象之后,再通过流的方式就可以将数据读取出来了。

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       val inputText = load()
       if (inputText.isNotEmpty()){
           editText.setText(inputText)
           editText.setSelection(inputText.length)
           Toast.makeText(this,"Restoring succeeded",Toast.LENGTH_SHORT).show()
       }

   }

   private fun load(): String{
       val content = StringBuilder()
       try {
           val input = openFileInput("data")
           val reader = BufferedReader(InputStreamReader(input))
           reader.use {
               reader.forEachLine {
                   content.append(it)
               }
           }
       } catch (e: IOException){
           e.printStackTrace()
       }
       return content.toString()
   }

注意,这里从文件中读取数据使用了一个 forEachLine 函数,这也是 Kotlin 提供的一个内置扩展函数,它会将读到的每行内容都回调到 Lambda 表达式中,我们在 Lambda 表达式中完成拼接逻辑即可。

可以看到,这里的思路非常简单,在 onCreate() 方法中调用 load() 方法读取文件中存储的文本内容,如果读到的内容不为空,就调用 EditText的setText() 方法将内容填充到 EditText 里,并调用 setSelection() 方法将输入光标移动到文本的末尾位置以便继续输入,然后弹出一句还原成功的提示。

现在重新运行一下程序,刚才保存的 Something important 字符串肯定会被填充到 EditText 中,然后编写一点其他的内容,比如在 EditText 中输入 “Hello world” ,接着按下 Back 键退出程序,再重新启动程序,这时刚才输入的内容并不会丢失,而是还原到了 EditText 中。

这样我们就已经把文件存储方面的知识学习完了,其实所用到的核心技术就是 Context 类中提供的 openFileInput()openFileOutput() 方法,之后就是利用各种流来进行读写操作。

不过,正如我前面所说,文件存储的方式并不适合用于保存一些较为复杂的结构型数据,因此,下面我们就来学习一下Android中另一种数据持久化的方式,它比文件存储更加简单易用,而且可以很方便地对某些指定的数据进行读写操作。

SharedPreferences 存储

不同于文件的存储方式,SharedPreferences 是使用键值对的方式来存储数据的。也就是说,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。而且SharedPreferences 还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型;如果存储的数据是一个字符串,那么读取出来的数据仍是字符串。

将数据存储到SharedPreferences 中

要想使用SharedPreferences 存储数据,首先需要获取SharedPreferences 对象。Android中主要提供了一下两种方法用于得到SharedPreferences 对象。

Context类中的 getSharedPreferences() 方法

此方法接收两个参数:第一个参数用于指定 SharedPreferences 文件的名称,如果指定的文件不存在则会创建一个, SharedPreferences 文件都是存放在 /data/data//shared_prefs/ 目录下的;第二个参数用于指定操作模式,目前只有默认的 MODE_PRIVATE 这一种模式可选,它和直接传入0的效果是相同的,表示只有当前的应用程序才可以对这个 SharedPreferences 文件进行读写。其他几种操作模式均已被废弃, MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 这两种模式是在Android 4.2版本中被废弃的, MODE_MULTI_PROCESS 模式是在Android 6.0版本中被废弃的。

Activity类中的 getPreferences() 方法

这个方法和 Context 中的 getSharedPreferences() 方法很相似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前 Activity 的类名作为 SharedPreferences 的文件名。

得到了 SharedPreferences 对象之后,就可以开始向 SharedPreferences 文件中存储数据了,主要可以分为3步实现。

  • (1) 调用 SharedPreferences 对象的 edit() 方法获取一个 SharedPreferences.Editor 对象。
  • (2) 向 SharedPreferences.Editor 对象中添加数据,比如添加一个布尔型数据就使用 putBoolean() 方法,添加一个字符串则使用 putString() 方法,以此类推。
  • (3) 调用 apply() 方法将添加的数据提交,从而完成数据存储操作。新建一个 SharedPreferencesTest 项目,然后修改 activity_main.xml 中的代码。


   

然后修改 MainActivity 中的代码:

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       saveButton.setOnClickListener {
           val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
           editor.putString("name", "Tom")
           editor.putInt("age", 28)
           editor.putBoolean("married", false)
           editor.apply()
       }
   }
}

可以看到,这里首先给按钮注册了一个点击事件,然后在点击事件中通过 getSharedPreferences() 方法指定 SharedPreferences 的文件名为 data ,并得到了 SharedPreferences.Editor 对象。接着向这个对象中添加了3条不同类型的数据,最后调用 apply() 方法进行提交,从而完成了数据存储的操作。

为了证实一下,我们还是要借助 Device File Explorer 来进行查看。打开 Device File Explorer ,然后进
/data/data/com.example.filepresistencetest/shared_prefs/ 目录下,可以看到生成了一个 data.xml 文件,

从 SharedPreferences 中读取数据

SharedPreferences 文件中读取数据会更加简单。 SharedPreferences 对象中提供了一系列的 get 方法,用于读取存储的数据,每种 get 方法都对应了
SharedPreferences.Editor 中的一种 put 方法,比如读取一个布尔型数据就使用 getBoolean() 方法,读取一个字符串就使用 getString() 方法。这些 get 方法都接收两个参数:第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。
修改 activity_main.xml 中的代码:



   

MainActivity中的代码:

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
         ...
       restoreButton.setOnClickListener {
           val prefs = getSharedPreferences("data",Context.MODE_PRIVATE)
           val name = prefs.getString("name","")
           val age = prefs.getInt("age",0)
           val married = prefs.getBoolean("married",false)
           Log.d("MainActivity", "name is $name")
           Log.d("MainActivity", "age is $age")
           Log.d("MainActivity", "married is $married")
       }
   }
}

可以看到,我们在还原数据按钮的点击事件中首先通过 getSharedPreferences() 方法得到了 SharedPreferences 对象,然后分别调用它的 getString()getInt()getBoolean() 方法,去获取前面所存储的姓名、年龄和是否已婚,如果没有找到相应的值,就会使用方法中传入的默认值来代替,最后通过Log将这些值打印出来。

所有之前存储的数据都成功读取出来了!通过这个例子,我们就把 SharedPreferences 存储的知识学习完了。相比之下, SharedPreferences 存储确实要比文本存储简单方便了许多,应用场景也多了不少,比如很多应用程序中的偏好设置功能其实就使用到了 SharedPreferences 技术。那么下面实现一个记住密码的功能。

SQLite 数据库存储

Android系统是内置了数据库的。
SQLite 是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百KB的内存就足够了,因而特别适合在移动设备上使用。 SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的ACID事务,所以只要你以前使用过其他的关系型数据库,就可以很快地上手 SQLite 。而 SQLite 又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。Android正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。

文件存储和 SharedPreferences 存储毕竟只适用于保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,就会发现以上两种存储方式很难应付得
了。比如我们手机的短信程序中可能会有很多个会话,每个会话中又包含了很多条信息内容,并且大部分会话还可能各自对应了通讯录中的某个联系人。很难想象如何用文件或者 SharedPreferences 来存储这些数据量大、结构性复杂的数据。但是使用数据库就可以做得到。

你可能感兴趣的:(Android基础知识-3)