Android10.0實現(xiàn)本地音樂播放(附源碼下載)
1.概述
本篇文章僅是Android小白在寫一個小程序,內(nèi)容僅供參考,有很多不足之處希望各位大神指出,文章末尾有整個項目的下載,不需要幣,只求幫你們解決到問題的同時收獲到一顆小小的贊。這個項目中還有很多不足的地方,如:在按鍵中設(shè)置圖片文字,這些正常的應(yīng)該交給Handler處理,我只是粗略地完成這個項目。測試環(huán)境:Android10.0。實現(xiàn):自動播放下一首,正常音樂的功能,全屏顯示。
Android10.0是內(nèi)外分存了的,應(yīng)用是沒有權(quán)限讀取內(nèi)存的,需要在配置文件中application中加上屬性:android:requestLegacyExternalStorage=“true”,不加可能可以讀取歌曲,但是無法播放。
2.效果截圖
截圖顯示不同是因為這不是同一時間截的,只是一個效果圖


3.讀取本地音樂以及保存歌曲
①先在AndroidManifest文件里面配置權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
②目前基本上的手機使用靜態(tài)權(quán)限是不夠的,需要動態(tài)獲取權(quán)限,因此需要在MainActivity里面動態(tài)獲取,在onCreate方法里調(diào)用方法
private void check(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
Log.d(TAG,"---------------------寫權(quán)限不夠-----------------");
}
if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED ){
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 2);
Log.d(TAG,"---------------------讀權(quán)限不夠-----------------");
}
}
}
③再去實現(xiàn)權(quán)限的回調(diào)方法,與Activity的onCreate方法是同一級別的
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "---------------------寫權(quán)限夠了-----------------------------");
}
break;
case 2:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "---------------------讀權(quán)限夠了-----------------------------");
}
break;
}
}
④創(chuàng)建一個工具類Mp3Info,用來保存音樂信息的,里面主要是一些get和set方法
public class Mp3Info {
private String url;//路徑
private String title;//歌曲名
private String artist;//藝術(shù)家
private long duration;//歌曲時長
private long id;
private long album;//專輯圖片
}
⑤創(chuàng)建一個MusicUtil類,通過ContentPorvider的接口獲取歌曲信息
public class MusicUtil {
//獲取專輯封面的UI
private static final String TAG="MusicUtil";
private static final Uri albumArtUri=Uri.parse("content://media/external/audio/albumart");
//生成歌曲列表
public static List<Mp3Info> getMp3InfoList(Context context){
Cursor cursor=context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,null);
List<Mp3Info> mp3InfoList=new ArrayList<>();
while(cursor.moveToNext()){
Mp3Info mp3Info=new Mp3Info();
mp3Info.setUrl(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)));//path
mp3Info.setTitle(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)));
mp3Info.setArtist(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)));
mp3Info.setDuration(cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)));
mp3Info.setId(cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID)));
mp3Info.setAlbum(cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)));
mp3InfoList.add(mp3Info);
}
return mp3InfoList;
}
//格式化時間,轉(zhuǎn)換為分/秒
public static String formatTime(long time){
String min = time / (1000 * 60) + "";
String sec = time % (1000 * 60) + "";
if (min.length() < 2) {
min = "0" + time / (1000 * 60) + "";
} else {
min = time / (1000 * 60) + "";
}
if (sec.length() == 4) {
sec = "0" + (time % (1000 * 60)) + "";
} else if (sec.length() == 3) {
sec = "00" + (time % (1000 * 60)) + "";
} else if (sec.length() == 2) {
sec = "000" + (time % (1000 * 60)) + "";
} else if (sec.length() == 1) {
sec = "0000" + (time % (1000 * 60)) + "";
}
return min + ":" + sec.trim().substring(0, 2);
}
//獲取專輯圖片,目前是只能獲取手機自帶歌曲的專輯圖片,如果手機有酷狗,qq音樂之類的,可能無法獲取專輯圖片
//因為他們的uri不知道。
public Bitmap getArtwork(Context context, long song_id, long album_id, boolean allowdefalut, boolean small){
if(album_id < 0) {
if(song_id < 0) {
Bitmap bm = getArtworkFromFile(context, song_id, -1);
if(bm != null) {
return bm;
}
}
if(allowdefalut) {
return getDefaultArtwork(context, small);
}
return null;
}
ContentResolver res = context.getContentResolver();
Uri uri = ContentUris.withAppendedId(albumArtUri, album_id);
if(uri != null) {
InputStream in = null;
try {
in = res.openInputStream(uri);
BitmapFactory.Options options = new BitmapFactory.Options();
//先制定原始大小
options.inSampleSize = 1;
//只進行大小判斷
options.inJustDecodeBounds = true;
//調(diào)用此方法得到options得到圖片的大小
BitmapFactory.decodeStream(in, null, options);
/** 我們的目標(biāo)是在你N pixel的畫面上顯示。 所以需要調(diào)用computeSampleSize得到圖片縮放的比例 **/
/** 這里的target為800是根據(jù)默認(rèn)專輯圖片大小決定的,800只是測試數(shù)字但是試驗后發(fā)現(xiàn)完美的結(jié)合 **/
if(small){
options.inSampleSize = computeSampleSize(options, 40);
} else{
options.inSampleSize = computeSampleSize(options, 600);
}
// 我們得到了縮放比例,現(xiàn)在開始正式讀入Bitmap數(shù)據(jù)
options.inJustDecodeBounds = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
in = res.openInputStream(uri);
return BitmapFactory.decodeStream(in, null, options);
} catch (FileNotFoundException e) {
Bitmap bm = getArtworkFromFile(context, song_id, album_id);
if(bm != null) {
if(bm.getConfig() == null) {
bm = bm.copy(Bitmap.Config.RGB_565, false);
if(bm == null && allowdefalut) {
return getDefaultArtwork(context, small);
}
}
} else if(allowdefalut) {
bm = getDefaultArtwork(context, small);
}
return bm;
} finally {
try {
if(in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 從文件當(dāng)中獲取專輯封面位圖
* @param context
* @param songid
* @param albumid
* @return
*/
private static Bitmap getArtworkFromFile(Context context, long songid, long albumid){
Bitmap bm = null;
if(albumid < 0 && songid < 0) {
throw new IllegalArgumentException("---------------------"+TAG+"Must specify an album or a song id");
}
try {
BitmapFactory.Options options = new BitmapFactory.Options();
FileDescriptor fd = null;
if(albumid < 0){
Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart");
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
if(pfd != null) {
fd = pfd.getFileDescriptor();
}
} else {
Uri uri = ContentUris.withAppendedId(albumArtUri, albumid);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
if(pfd != null) {
fd = pfd.getFileDescriptor();
}
}
options.inSampleSize = 1;
// 只進行大小判斷
options.inJustDecodeBounds = true;
// 調(diào)用此方法得到options得到圖片大小
BitmapFactory.decodeFileDescriptor(fd, null, options);
// 我們的目標(biāo)是在800pixel的畫面上顯示
// 所以需要調(diào)用computeSampleSize得到圖片縮放的比例
options.inSampleSize = 100;
// 我們得到了縮放的比例,現(xiàn)在開始正式讀入Bitmap數(shù)據(jù)
options.inJustDecodeBounds = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
//根據(jù)options參數(shù),減少所需要的內(nèi)存
bm = BitmapFactory.decodeFileDescriptor(fd, null, options);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bm;
}
/**
* 獲取默認(rèn)專輯圖片
* @param context
* @return
*/
@SuppressLint("ResourceType")
public static Bitmap getDefaultArtwork(Context context, boolean small) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.RGB_565;
if(small){ //返回小圖片
//return
BitmapFactory.decodeStream(context.getResources().openRawResource(R.drawable.default_picture), null, opts);
}
return BitmapFactory.decodeStream(context.getResources().openRawResource(R.drawable.default_picture), null, opts);
}
/**
* 對圖片進行合適的縮放
* @param options
* @param target
* @return
*/
public static int computeSampleSize(BitmapFactory.Options options, int target) {
int w = options.outWidth;
int h = options.outHeight;
int candidateW = w / target;
int candidateH = h / target;
int candidate = Math.max(candidateW, candidateH);
if(candidate == 0) {
return 1;
}
if(candidate > 1) {
if((w > target) && (w / candidate) < target) {
candidate -= 1;
}
}
if(candidate > 1) {
if((h > target) && (h / candidate) < target) {
candidate -= 1;
}
}
return candidate;
}
}
⑥為列表設(shè)置adapter,新建一個MyAdapter類繼承BaseAdapter,然后在重寫的getView里面設(shè)置顯示的控件
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
holder=new ViewHolder();
convertView=View.inflate(context, R.layout.list_item,null);
holder.tv_title=convertView.findViewById(R.id.tv_title);
holder.tv_artist=convertView.findViewById(R.id.tv_artist);
holder.tv_duration=convertView.findViewById(R.id.tv_duration);
holder.tv_position=convertView.findViewById(R.id.tv_position);
convertView.setTag(holder);
}else {
holder= (ViewHolder) convertView.getTag();
}
holder.tv_title.setText(list.get(position).getTitle());
holder.tv_artist.setText(list.get(position).getArtist());
long duration = list.get(position).getDuration();
String time= MusicUtil.formatTime(duration);
holder.tv_duration.setText(time);
holder.tv_position.setText(position+1+"");
if(currentItem == position){
holder.tv_title.setSelected(true);
holder.tv_position.setSelected(true);
holder.tv_duration.setSelected(true);
holder.tv_artist.setSelected(true);
}else{
holder.tv_title.setSelected(false);
holder.tv_position.setSelected(false);
holder.tv_duration.setSelected(false);
holder.tv_artist.setSelected(false);
}
return convertView;
}
class ViewHolder{
TextView tv_title;//歌曲名
TextView tv_artist;//歌手
TextView tv_duration;//時長
TextView tv_position;//序號
}
4.使用Service實現(xiàn)后臺播放
使用的是bindService,這樣Service的生命周期就和Activity的生命周期綁定在一起了。創(chuàng)建一個MusicService。注意:銷毀Service的時候需要將音樂對象release。
①Service實現(xiàn)功能,在onBind方法里面實例化音樂播放對象
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind is call");
myBinder=new MyBinder();
return myBinder;
}
②在MyBinder()里面實現(xiàn)音樂的各種功能,使用的是內(nèi)部類,初始化部分請看源代碼包
public class MyBinder extends Binder{
private int index=0;//歌曲索引
//播放音樂
public void playMusic(int index){
this.index=index;
try {
File file=new File(list.get(this.index).getUrl());
if(!file.exists()){
Log.d(TAG,"------------------------------文件不存在------------------------------");
return ;
}else{
Log.d(TAG,"------------------------------文件:"+file.getPath()+"存在 ------------------------------");
}
if(mediaPlayer!=null){
mediaPlayer.reset();
mediaPlayer.release();
}
mediaPlayer=new MediaPlayer();
String str=list.get(this.index).getUrl();
mediaPlayer.setDataSource(str);
Log.d(TAG,list.get(this.index).getUrl()+"");
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
//暫停音樂
public void pauseMusic(){
if(mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
//關(guān)閉音樂
public void closeMusic(){
if(mediaPlayer!=null){
mediaPlayer.release();
}
}
//下一首
public void nextMusic(){
if(index>=list.size()-1){
this.index=0;
}else{
this.index+=1;
}
playMusic(this.index);
}
//上一首
public void preciousMusic(){
if(index<=0){
this.index=list.size()-1;
}else{
this.index-=1;
}
playMusic(this.index);
}
//獲取歌曲時長
public int getProgress(int dex){
return (int)list.get(dex).getDuration();
}
public int getProgress(){
return (int)list.get(index).getDuration();
}
//獲取當(dāng)前播放位置
public int getPlayPosition(){
return mediaPlayer.getCurrentPosition();
}
//移動到當(dāng)前點播放
public void seekToPosition(int m){
mediaPlayer.seekTo(m);
}
}
③在MainActivity里面綁定
a.先實例化一個ServiceConnection對象
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder= (MusicService.MyBinder) service;
seekBar.setMax(myBinder.getProgress());
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//這里是判斷進度條移動是不是用戶所為
if(fromUser){
myBinder.seekToPosition(seekBar.getProgress());
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
handler.post(runnable);
Log.d(TAG, "Service與Activity已連接");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
b.還需要一個handler來控制ui組件的變化,實例化放在了onCreate方法里面。
c.用一個Runnable對象進行seekbar的前進
private Runnable runnable=new Runnable() {
@Override
public void run() {
seekBar.setProgress(myBinder.getPlayPosition());
tv_leftTime.setText(time.format(myBinder.getPlayPosition())+"");
tv_rightTime.setText(time.format(myBinder.getProgress()-myBinder.getPlayPosition())+"");
if(myBinder.getProgress()-myBinder.getPlayPosition()<1000){//時間不夠了自動觸發(fā)下一首
runOnUiThread(new Runnable() {//使用ui線程來觸發(fā)按鍵點擊事件,不知道這樣有沒有什么危害
@Override
public void run() {
ib_next.performClick();
}
});
}
handler.postDelayed(runnable,1000);
}
};
d.在onCreate方法里進行綁定
MediaServiceIntent =new Intent(this,MusicService.class);//MediaServiceIntent為一個Intent bindService(MediaServiceIntent,connection,BIND_AUTO_CREATE);
5.使用Notification通知欄通知
注意::如果點擊通知欄是從MainActivity跳轉(zhuǎn)到MainActivity,需要在配置文件的activity android:name=".MainActivity"
android:launchMode=“singleTask”,設(shè)置為單任務(wù)。
布局在源代碼包里,在Api26級以上需要使用NotificationChannel
①設(shè)置通知所觸發(fā)的PandingIntent,通過Action識別,action為自己定義的常量,setSound無聲音。通過RemoteViews去實現(xiàn)通知欄組件的按鈕實現(xiàn)
//設(shè)置通知
private void setNotification(){
String channelID="cary";
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
NotificationChannel channel=new NotificationChannel(channelID,"xxx",NotificationManager.IMPORTANCE_LOW);
manager.createNotificationChannel(channel);
}
Intent intent=new Intent(MainActivity.this,MainActivity.class);
PendingIntent pi=PendingIntent.getActivity(MainActivity.this,0,intent,0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notify=new Notification.Builder(MainActivity.this,channelID)
.setWhen(System.currentTimeMillis())
.setSound(null)
.build();
}
notify.icon=android.R.drawable.btn_star;
notify.contentIntent=pi;
notify.contentView=remoteViews;
notify.flags=Notification.FLAG_ONGOING_EVENT;
remoteViews.setOnClickPendingIntent(R.id.notice,pi);
//上一首
Intent prevIntent=new Intent(BUTTON_PREV_ID);
PendingIntent prevPendingIntent=PendingIntent.getBroadcast(this,0,prevIntent,0);
remoteViews.setOnClickPendingIntent(R.id.widget_prev,prevPendingIntent);
//播放暫停
Intent playIntent=new Intent(BUTTON_PLAY_ID);
PendingIntent playPendingIntent=PendingIntent.getBroadcast(this,0,playIntent,0);
remoteViews.setOnClickPendingIntent(R.id.widget_play,playPendingIntent);
//下一首
Intent nextIntent=new Intent(BUTTON_NEXT_ID);
PendingIntent nextPendingIntent=PendingIntent.getBroadcast(this,0,nextIntent,0);
remoteViews.setOnClickPendingIntent(R.id.widget_next,nextPendingIntent);
//關(guān)閉
Intent closeIntent=new Intent(BUTTON_CLOSE_ID);
PendingIntent closePendingIntent=PendingIntent.getBroadcast(this,0,closeIntent,0);
remoteViews.setOnClickPendingIntent(R.id.widget_close,closePendingIntent);
}
②動態(tài)注冊廣播
//注冊廣播
private void initButtonReceiver(){
buttonBroadcastReceiver=new ButtonBroadcastReceiver();
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(BUTTON_PREV_ID);
intentFilter.addAction(BUTTON_PLAY_ID);
intentFilter.addAction(BUTTON_NEXT_ID);
intentFilter.addAction(BUTTON_CLOSE_ID);
registerReceiver(buttonBroadcastReceiver,intentFilter);
}
③顯示廣播,需要注意的是,每次在Activity里面點擊上一首或者下一首都需要調(diào)用這個方法,刷新通知欄的標(biāo)題,以及狀態(tài)專輯
//展示通知
private void showNotification(){
if(isPlaying){
remoteViews.setImageViewResource(R.id.widget_play,R.drawable.stop);
}else{
remoteViews.setImageViewResource(R.id.widget_play,R.drawable.start);
}
remoteViews.setImageViewBitmap(R.id.widget_album,utils.getArtwork(MainActivity.this,list.get(music_index).getId(),list.get(music_index).getAlbum(),true,false));
remoteViews.setImageViewResource(R.id.widget_close,android.R.drawable.ic_menu_close_clear_cancel);
remoteViews.setTextViewText(R.id.widget_title,list.get(music_index).getTitle());
remoteViews.setTextViewText(R.id.widget_artist,list.get(music_index).getArtist());
remoteViews.setTextColor(R.id.widget_title,Color.BLACK);
remoteViews.setTextColor(R.id.widget_artist,Color.BLACK);
notify.contentView=remoteViews;
manager.notify(100,notify);
}
④通知欄動作接收,使用的是內(nèi)部類
public class ButtonBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
Log.d(TAG,"--------------------收到action:"+action+"--------------------------");
if(action.equals(BUTTON_PREV_ID)){
runOnUiThread(new Runnable() {
@Override
public void run() {
ib_precious.performClick();
return;
}
});
}
if(action.equals(BUTTON_PLAY_ID)){
runOnUiThread(new Runnable() {
@Override
public void run() {
ib_state.performClick();
return;
}
});
}
if(action.equals(BUTTON_NEXT_ID)){
runOnUiThread(new Runnable() {
@Override
public void run() {
ib_next.performClick();
return;
}
});
}
if(action.equals(BUTTON_CLOSE_ID)){
handler.removeCallbacks(runnable);
myBinder.closeMusic();
unbindService(connection);
if(remoteViews!=null){
manager.cancel(100);
}
unregisterReceiver(buttonBroadcastReceiver);
finish();
}
}
}
6.全屏顯示
①在AndroidManifest文件里面配置主題樣式android:theme="@style/Theme.AppCompat.Light.NoActionBar">
然后在onCreate方法里在setContentView(R.layout.activity_main);之前
設(shè)置:
if(Build.VERSION.SDK_INT>=21){
View decorView=getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
7.設(shè)置歌曲選中后的樣式
①在res目錄下的drawable資源下新建一個類型為selector的xml文件,里面設(shè)置屬性
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_selected="false" android:color="#FFFFFF"/> <item android:state_selected="true" android:color="#FF7F00"/> </selector>
②在Adapter里面設(shè)置getView
currentItem == position){
holder.tv_title.setSelected(true);
holder.tv_position.setSelected(true);
holder.tv_duration.setSelected(true);
holder.tv_artist.setSelected(true);
}else{
holder.tv_title.setSelected(false);
holder.tv_position.setSelected(false);
holder.tv_duration.setSelected(false);
holder.tv_artist.setSelected(false);
}
注意:在使用的時候可能需要手動去設(shè)置里面打開權(quán)限
代碼包里面的Music_Player\app\release下的MusicPlayer.apk是app安裝包哦,期待您的點贊,與評論
到此這篇關(guān)于Android10.0實現(xiàn)本地音樂播放(附源碼下載)的文章就介紹到這了,更多相關(guān)Android10.0本地音樂播放內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java、android可用的rtp封包解包h264案例
- Android下拉刷新ListView——RTPullListView(demo)
- Android GSYVideoPlayer視頻播放器功能的實現(xiàn)
- Android實現(xiàn)本地Service方法控制音樂播放
- Android項目實現(xiàn)視頻播放器
- Android Studio實現(xiàn)音樂播放器
- Android Studio實現(xiàn)簡單音樂播放功能的示例代碼
- Android實現(xiàn)文字滾動播放效果的代碼
- Android仿優(yōu)酷視頻的懸浮窗播放效果
- Android原生視頻播放VideoView的使用
- Android手機通過rtp發(fā)送aac數(shù)據(jù)給vlc播放的實現(xiàn)步驟
相關(guān)文章
ViewPager和SlidingPaneLayout的滑動事件沖突解決方法
下面小編就為大家分享一篇ViewPager和SlidingPaneLayout的滑動事件沖突解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
android組件SwipeRefreshLayout下拉小球式刷新效果
這篇文章主要為大家詳細介紹了android組件SwipeRefreshLayout下拉小球式刷新效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02
Android應(yīng)用圖標(biāo)在狀態(tài)欄上顯示實現(xiàn)原理
Android應(yīng)用圖標(biāo)在狀態(tài)欄上顯示,以及顯示不同的圖標(biāo),其實很研究完后,才發(fā)現(xiàn),很簡單,具體實現(xiàn)如下,感興趣的朋友可以參考下哈2013-06-06
解決Android studio 3.6.1 出現(xiàn)Cause: unable to find valid certifi
這篇文章主要介紹了Android studio 3.6.1 出現(xiàn)Cause: unable to find valid certification path to requested target 報錯的問題及解決方法,需要的朋友可以參考下2020-03-03
Android 靜默方式實現(xiàn)批量安裝卸載應(yīng)用程序的深入分析
本篇文章是對Android 靜默方式實現(xiàn)批量安裝卸載應(yīng)用程序進行了詳細的分析介紹,需要的朋友參考下2013-06-06

