热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

GettingStartedSavingData

绝大多数的AndroidApp都需要保存数据,即使仅仅在onPause()方法里保存app状态信息以免user进度信息被丢失。大多数非著名的app也可能要保存用户设置

        绝大多数的Android App都需要保存数据,即使仅仅在onPause()方法里保存app状态信息以免user进度信息被丢失。大多数非著名的app也可能要保存用户设置信息。一些应用需要在文件和数据库保存大量的信息。本文将向你讲述Android主要的数据存储方式。包括:

  • 在SharedPreferences文件里保存简单数据类型的key-value键值对。
  •  在Android文件系统里保存任意类型的文件。
  • 使用SQLite数据库

        Lessons

          Saving Key-Value Sets

              Learn to use a shared preferences file for storing small amounts of information in key-value pairs.

          Saving Files 

               Learn to save a basic file, such as to store long sequences of data that are generally read in                        order

           Saving Data in SQL Databases 

              Learn to use a SQLite database to read and write structured data.

 

 

              

    Saving Key-Value Sets

          如果你有少量的key-value数据需要保存,你应该使用SharePreferences APIs。一个SharedPreferences对象指向一个包含key-value对的文件,并提供了简单的方法读写它们。SDK框架管理每个SharedPreferences文件,你可以指定该文件是私有的还是共享的。

         本文告诉你如何使用SharedPreferences的APIs存储和检索简单的值。

        注: SharedPreferences  APIs仅仅用于读和写键-值对,你不应该使用Preference APIs混淆它们。Preference是一个帮助你处理你的app设置的用户界面(虽然SharePrerences是保存用户设置的preference界面的一个默认实现类)。更多如何使用Preference APIs的信息,参见Settings 向导。

 

       Get a Handle to a SharedPreferences

       你能调用如下两个方法之一产生一个新的shared preferences文件或者访问一个已存在的sharedPreferences文件。

  •   getSharedPreferences()  — 如果需要多个用名字标识的sharedpreferences文件使用该方法,你能用第一个参数指定文件名字。你必须从你的app里的任何Context上调用该方法。
  •   getPreferences()  — 该方法在Activity上调用,如果在该Activity里你只需要一个sharedPreferences文件可以调用该方法。因为该方法检索一个默认的sharedPreferences。该默认文件属于调用该方法的activity,你不需要提供一个名字。

        例如,下面的代码在一个fragment里执行。它访问一个由R.string.preference_file_key字符串资源指定的sharedPreferences文件。该文件已私有模式访问,因此,仅仅你的app能访问该文件。

       

Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(getString(R.string.preference_file_key), Context.MODE_PRIVATE);

         

        当命名你的sharedPreferences文件时,你应该使用一个你的app范围内唯一标识的名字。例如“com.example.myapp.PREFERENCE_FILE_KEY”。

        或者,你仅仅在某个activity里只需要一个shared preferences文件,你能使用getPreferences()方法:

       

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

         注意:如果你用MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE模式产生一个shared preferences文件,那么任何知道该文件名的app都能访问你的数据。

        

        Write to Shared Preferences

        为了写数据到shared preferences文件,调用SharedPreferences的edit()方法产生一个SharedPreferences.Editor对象。

        调用例如putInt()或者putString()方法传递要写入的key-value值,然后调用commit()方法保存这些值。例如:

       

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

         

        Read from Shared Preferences

        为了从shared preferences文件读取数据,调用getInt()或者getString()等方法,提供你要的数据的key值,如果key不存在,将返回一个默认值。例如:

       

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue);

 

   

        

   Saving Files

        Android使用的文件系统类似于其他平台的基于磁盘的文件系统。本文描述如何使用Android的文件系统和 File APIs读和写文件。

        File对象适合从头到尾没有调过的读和写大量的数据。例如,图片文件和网络交互的数据。

        本文讲述在你的app里如何执行基本的文件相关的操作。本文假设你对Linux文件系统基础和java.io包里的标准文件输入/输出APIs很熟悉。

        

        Choose Internal or External Storage

        所有的Android设备有两个文件存储区域:“internal”和“external”存储。这些名字来自于Android早期,当时大多数设备提供内置的非易失的内存(内部存储),同时提供一个可移除存储媒介例如一个micro SD卡(外部存储)。一些设备把永久存储空间划分为“internal” 和 “external”部分,因此,甚至没有一个可移除的存储媒介,也总是存在“internal” 和 “external”两个存储空间,且外部存储不管是不是可移除的,API行为都是相同的。

        

         提示:虽然app默认的安装到内置存储,但你能在manifest文件里指定android:installLocation 属性让你的app安装到外部存储。当APK大小是非常大,同时用户有外置存储,并且外置存储比内置存储空间更大时,需要可能会需要改选项。

        

        Obtain Permissions for External Storge

        为了写到外部存储,你必须在manifest file里添加WRITE_EXTERNAL_STORAGE权限:

        

...

         注意:当前,app不用指定专门的权限而去读外部存储。然而,以后发版的android版本将改变这点。如果你的app需要读(但不写)外部存储,那么你将需要声明READ_EXTERNAL_STORAGE权限。为了确保你的应用能正常的运行,你应该总是声明该权限,即使该改变还没有生效。

       

...

        然而,如果你的app使用WRITE_EXTERNAL_STORAGE权限,那么,它隐式的包含有读外部存储的权限。

     

        在内部存储上保存文件你不需要任何权限。你的app始终有读和写内部存储文件的权限。

  

        Save a File on Internal Storage

        当保存文件到内部存储时,你能调用下面两个方法里的一个获取合适的文件目录。

 

       getFilesDir()

        返回一个代表你的应用app的内部目录的文件。

 

getCacheDir()

 

        返回一个内部存储目录的文件作为你的app的临时缓存文件。一定要一旦该缓存文件不再需要时删除该文件,从而实现合理的内存大小限制,例如1M。如果系统开始变的存储紧张,系统将不给你任何警告的情况下删除你的缓存文件

        

        为了在这些目录中产生新的文件,你能使用File()构造器,传递上面两个方法中的一个作为你的内部存储目录,例如:

        

File file = new File(context.getFilesDir(), filename);           你能调用openFileOutput()方法得到一个FileOutputStream流,然后向你的内部存储文件里写数据。例如,下面是如何写一些文本到一个文件。

 

         

String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try { outputStream = openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(string.getBytes()); outputStream.close(); } catch (Exception e) { e.printStackTrace(); }

         或者,如果你需要缓存一些文件,你应该使用createTempFile()方法。例如,下面的方法从一个URL里抽取文件名,然后用你的app内存缓存目录名产生一个文件。

        

public File getTempFile(Context context, String url) { File file; try { String fileName = Uri.parse(url).getLastPathSegment(); file = File.createTempFile(fileName, null, context.getCacheDir()); catch (IOException e) { // Error while creating file } return file; }

         注:你的app的内部存储目录在android文件系统的一个特定的位置通过app的包名被指定。理论上,如果你设置你的内部文件为可读模式,其他的app能读取你的内部文件。当然,其他的app也将需要知道你的app的包名和文件名。其他的应该不能浏览你的内部存储目录,也没有读写访问权限,除非你显示的设置文件为可读和可写模式。因此一旦你设置你的内存存储中的文件为MODE_PRIVATE模式,其他的app将不能访问。

 

        因为外部存储可能不是总是有效的——例如当用户挂载存储到PC或者移除作为外部存储的SD卡时——你应该在访问外部存储之前总是检查该卷是否有效。你能调用getExternalStorageState()方法插叙外部存储状态。如果返回的状态值等于MEDIA_MOUNTED,那么你能读写外部存储文件。例如,下面的方法被用于检查存储是否有效:
        

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {String state = Environment.getExternalStorageState();if (Environment.MEDIA_MOUNTED.equals(state)) {return true;}return false;
}/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {String state = Environment.getExternalStorageState();if (Environment.MEDIA_MOUNTED.equals(state) ||Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {return true;}return false;
}
 

 

          虽然外部存储能被用户或者其他的app修改,有两类文件你能保存在外部存储上:
          Public files

                文件时完全的公开的, 对其他的app和用户是完全可见的。当用户卸载你的app,这些文件应该仍                 然保留,对用户有效。

                例如:用户拍的照片或者下载的文件。

          Private files  

                指仅仅属于你的app,当用户卸载你的app时应该被删除的文件。虽然理论上这些文件也可以被用                   户或者其他的app访问,因为它们在外部存储。当用户卸载你的app时,系统应该删除所有的外部                   存储里私有目录下的所有文件。

                  例如,你的app下载的额外资源或者临时媒体文件。

 

         如果你想在外部存储保存public的文件,使用getExternalStoragePublicDirectory()方法获取SD卡上合适的public目录。该方法接受一个指定文件类型的参数,以便于该文件和其他的公共的文件能逻辑地组织,例如DIRECTORY_MUSIC或者DIRECTORY_PICTURES。如下:

        

public File getAlbumStorageDir(String albumName) {// Get the directory for the user's public pictures directory. File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName);if (!file.mkdirs()) {Log.e(LOG_TAG, "Directory not created");}return file;
}

         如果你想要保存文件是私有的,你能通过调用getExternalFilesDir()方法取得合适的私有目录,传递一个表明目录类型的名字。这种方式产生的目录被添加到一个包含了你的app所有私有文件的父目录。这些,当用户卸载你的app时系统会删除所有的私有文件。

        例如,如下是一个产生个人相片册目录的方法:

        

public File getAlbumStorageDir(Context context, String albumName) {// Get the directory for the app's private pictures directory. File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName);if (!file.mkdirs()) {Log.e(LOG_TAG, "Directory not created");}return file;
}

         如果系统预先定义的子目录没有一个适合你的文件,你能调用getExternalFilesDir()方法,然后给该方法传递null值。这样系统返回你的app私有文件目录的根目录。

 

        记住通过getExternalFileDir()产生的目录,当用户卸载你的app时,该目录下的文件和该目录会被删除。

如果你想保存的文件在app卸载后仍然有效——例如你的app是一个照相机应用,用户想要保存照片——你应该使用getExternalStoragePublicDirectory().方法。

        不管你是使用getExternalStoragePublicDirectory()方法产生共享文件还是使用getExternalFilesDir()产生私有文件,使用系统提供的API常量例如DIRECTORY_PICTURES作为目录是非常重要的。这些目录名确保文件被系统合理的处理和对待。例如,保存在 DIRECTORY_RINGTONES 里的文件能被系统media scanner分类作为铃声而非音乐。

 

      Query Free Space

      如果你提前知道你要保存多少数据,你可能需要知道是否有足够的空间是有效的,而不引起IOException.这可以通过调用getFreeSpace()或者getTotaolSpace()方法实现。该方法分别提供了存储卷当前的剩余空间和总空间大小。其他的情况下可需要知道SD卡的存储大小信息,例如空间信息在避免超出一定的存储阀值避免写入也是有用的。

        然而,系统并不能保存写入和getFreeSpace()返回值大小一样的字节值。如果剩余空间比你要保存的数据大小多几MB。或者文件系统剩余空间大于10%,那么保存文件操作是安全的。否则,你可能不适合想存储卷里写。

       注:当你不知道你保存的文件大小时,你不应该检查有效空间量,而是试着写文件,然后捕获IOException。例如你将文件从PNG图片格式转换到JPEG格式时,你并不知道转换后的文件大小。

  

        Delete a File

        你应该总是记得删除你不再需要的文件。删除文件最直接的方式就是有一个打开文件的应用,调用其上的delete()方法

        

myFile.delete();

         如果该文件保存在内部存储,你也能获取Contex,调用该对象的deleteFile()方法删除文件:

     

myContext.deleteFile(fileName);

         

       注:当用户卸载你的app时,Android系统会删除:

  •  你保存在内存存储的所有文件
  • 你通过调用getExternalFilesDir()方法保存在外部存储的文件

        然而,你也应该手动的删除调用getCacheDir() 方法产生的缓存文件,也应该删除其他的你不需要的文件。

         

 

       

      Saving Data in SQL Databases

       对于重复的或者结构化的数据保存数据到数据库是一个理想的方式,例如联系人信息。 本文假设你对SQL 数据库的一般知识是熟悉的,帮助你如何在Adnroid上使用SQLite数据库。Android上操作数据库的API在android.database.sqlite包下。

 

        Defina a Schema and Contract

        SQL数据库的主要原则之一是schema:数据库怎么被组织的格式化声明。schema反射到你创建数据库的SQL语句上。schema在产生companion类时是很有用的,例如contract类,其通过系统的和自描述方式显示的指定了你的schema的布局。

        contract类是一个定义URI,表和栏的常量的容器。contract类允许你在相同包里的不同类之间使用同样的常量。这使得你修改的你栏名只需要修改一个地方即可。

        组织contract类的一种好的方式是把数据库的全局的定义放在该类的根上,然后对每个表产生一个内部类来枚举该表的栏。

        注:通过实现BaseColumns接口,你的内部类能继承一个名为_ID的主键属性。Android里一些类例如cursor adaptor期望有该属性。这是不必须的,但是这能使得你的数据库在Android框架上工作和谐。

        例如,下面代码片段定义了表名和某个表的栏名:

        

public final class FeedReaderContract {// To prevent someone from accidentally instantiating the contract class,// give it an empty constructor.public FeedReaderContract() {}/* Inner class that defines the table contents */public static abstract class FeedEntry implements BaseColumns {public static final String TABLE_NAME = "entry";public static final String COLUMN_NAME_ENTRY_ID = "entryid";public static final String COLUMN_NAME_TITLE = "title";public static final String COLUMN_NAME_SUBTITLE = "subtitle";...}
}
 

 

        Create a Database Using a SQL Helper 

        一旦你已定义了你的数据库结构,你应该实现产生和维护数据库以及表的方法。下面是一些典型的产生和删除表的语句:

        

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES ="CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +FeedEntry._ID + " INTEGER PRIMARY KEY," +FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +... // Any other options for the CREATE command" )";private static final String SQL_DELETE_ENTRIES ="DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
 

 

        像保存在内部存储的文件,Android保存你的数据库在与你的app相关联的私有存储空间。这样你的数据是安全的,默认地,这块区域不能被其他的应用访问。

       一些操作数据库的有效API在类SQLiteOpenHelper里 。当你使用该类获取你的数据库引用时,系统可能执行一个耗时操作产生和更新数据库。仅仅在需要时执行而不是应用启动时,所有你需要做的是调用 getWritableDatabase() 或者getReadableDatabase().

        注:因为 getWritableDatabase() orgetReadableDatabase().是耗时操作,确保在后台线程调用它们,例如AsyncTask or IntentService。

        为了使用SQLiteOpenHelper,产生该类的子类,重写 onCreate()onUpgrade() and onOpen()回调方法。你可能也想要实现onDowngrade(),但是这不是必须的,视需要而定。

 

        例如,下面是SQLiteOpenHelper类的一个实现,其用到了上面介绍的一些命令:

        

public class FeedReaderDbHelper extends SQLiteOpenHelper {// If you change the database schema, you must increment the database version.public static final int DATABASE_VERSION = 1;public static final String DATABASE_NAME = "FeedReader.db";public FeedReaderDbHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}public void onCreate(SQLiteDatabase db) {db.execSQL(SQL_CREATE_ENTRIES);}public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {// This database is only a cache for online data, so its upgrade policy is// to simply to discard the data and start overdb.execSQL(SQL_DELETE_ENTRIES);onCreate(db);}public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {onUpgrade(db, oldVersion, newVersion);}
}
         为了访问你的数据库,实例化你的SQLiteOpenHelper:类的子类:

 

        

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());          

 

        Put Information into a Database

        通过传递ContentValues对象到insert()方法插入数据到数据库:

        

// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(FeedEntry.TABLE_NAME,FeedEntry.COLUMN_NAME_NULLABLE,values);
         insert()方法的第一个参数是表名。第二参数用于指定可以插入NULL值的栏名,以防ContentValues为空(如果你设置该参数为“null”,没有值将不能被插入)。

 

 

        Read Inforamtion from a Database

        为了从数据库里读取数据,使用query()方法,传递给该方法你的选择标准和期望的栏。该方法包含insert()和update()的元素,不包含定义你想要去的数据的栏名的行。查询结果以Cursor 对象返回。

        

SQLiteDatabase db = mDbHelper.getReadableDatabase();// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {FeedEntry._ID,FeedEntry.COLUMN_NAME_TITLE,FeedEntry.COLUMN_NAME_UPDATED,...};// How you want the results sorted in the resulting Cursor
String sortOrder =FeedEntry.COLUMN_NAME_UPDATED + " DESC";Cursor c = db.query(FeedEntry.TABLE_NAME, // The table to queryprojection, // The columns to returnselection, // The columns for the WHERE clauseselectionArgs, // The values for the WHERE clausenull, // don't group the rowsnull, // don't filter by row groupssortOrder // The sort order);
         为了获取Cursor里的一行,使用cursor的移动方法,在你从cursor读取数据时,你必须总是调用该方法。一般地,开始你应该调用moveToFirst()方法,该方法移动游标到结果的开始处。对于每一行,你可以调用Cursor的get方法获取某一个栏的值,例如  getString() or getLong(),对于get方法,你必须传递栏的索引位置给get方法,你能调用getColumnIndex() or getColumnIndexOrThrow()方法获取栏索引,例如:


        

cursor.moveToFirst();
long itemId = cursor.getLong(cursor.getColumnIndexOrThrow(FeedEntry._ID)
);
         


        Delete Information from a Database

         为了删除表里的某行,你需要提供表示行的选择标准。数据库API提供了产生选择标准的机制,该机制防止SQL注入。该机制将选择条件分成了选择条款和选择参数。条款定义了查看的栏。参数是绑定到选择从句的值。因为结果不是被规则的SQL语句处理,最小的减少了SQL注入。

        

// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, selection, selectionArgs);
 


       Update a Database

        当你需要修正你的数据库的部分字段的值时,使用update() 方法。

        Updating the table combines the content values syntax of insert() with the where syntax of delete().

SQLiteDatabase db = mDbHelper.getReadableDatabase();// New value for one column
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);// Which row to update, based on the ID
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };int count = db.update(FeedReaderDbHelper.FeedEntry.TABLE_NAME,values,selection,selectionArgs);
 


 

        



推荐阅读
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • Summarize function is doing alignment without timezone ?
    Hi.Imtryingtogetsummarizefrom00:00otfirstdayofthismonthametric, ... [详细]
  • ihaveusedthedelphidatabindingwizardwithmyxmlfile,andeverythingcompilesandrunsfine. ... [详细]
author-avatar
len1111_744
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有