Android10.0实现本地音乐播放(附源码下载)

1.概述

本篇文章仅是Android小白在写一个小程序,内容仅供参考,有很多不足之处希望各位大神指出,文章末尾有整个项目的下载,不需要币,只求帮你们解决到问题的同时收获到一颗小小的赞。这个项目中还有很多不足的地方,如:在按键中设置图片文字,这些正常的应该交给Handler处理,我只是粗略地完成这个项目。测试环境:Android10.0。实现:自动播放下一首,正常音乐的功能,全屏显示。
Android10.0是内外分存了的,应用是没有权限读取内存的,需要在配置文件中application中加上属性:android:requestLegacyExternalStorage=“true”,不加可能可以读取歌曲,但是无法播放。

2.效果截图

截图显示不同是因为这不是同一时间截的,只是一个效果图

3.读取本地音乐以及保存歌曲

①先在AndroidManifest文件里面配置权限

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

②目前基本上的手机使用静态权限是不够的,需要动态获取权限,因此需要在MainActivity里面动态获取,在onCreate方法里调用方法

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,"---------------------写权限不够-----------------");
  }
  if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED ){
   requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 2);
   Log.d(TAG,"---------------------读权限不够-----------------");
  }
 }
}

③再去实现权限的回调方法,与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, "---------------------写权限够了-----------------------------");
   }
   break;
  case 2:
   if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    Log.d(TAG, "---------------------读权限够了-----------------------------");
   }
   break;
 }
}

④创建一个工具类Mp3Info,用来保存音乐信息的,里面主要是一些get和set方法

public class Mp3Info {
 private String url;//路径
 private String title;//歌曲名
 private String artist;//艺术家
 private long duration;//歌曲时长
 private long id;
 private long album;//专辑图片
 }

⑤创建一个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;
 }
 //格式化时间,转换为分/秒
 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;
    //调用此方法得到options得到图片的大小
    BitmapFactory.decodeStream(in, null, options);
    /** 我们的目标是在你N pixel的画面上显示。 所以需要调用computeSampleSize得到图片缩放的比例 **/
    /** 这里的target为800是根据默认专辑图片大小决定的,800只是测试数字但是试验后发现完美的结合 **/
    if(small){
     options.inSampleSize = computeSampleSize(options, 40);
    } else{
     options.inSampleSize = computeSampleSize(options, 600);
    }
    // 我们得到了缩放比例,现在开始正式读入Bitmap数据
    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;
 }
 /**
  * 从文件当中获取专辑封面位图
  * @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;
   // 调用此方法得到options得到图片大小
   BitmapFactory.decodeFileDescriptor(fd, null, options);
   // 我们的目标是在800pixel的画面上显示
   // 所以需要调用computeSampleSize得到图片缩放的比例
   options.inSampleSize = 100;
   // 我们得到了缩放的比例,现在开始正式读入Bitmap数据
   options.inJustDecodeBounds = false;
   options.inDither = false;
   options.inPreferredConfig = Bitmap.Config.ARGB_8888;

   //根据options参数,减少所需要的内存
   bm = BitmapFactory.decodeFileDescriptor(fd, null, options);
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  }
  return bm;
 }

 /**
  * 获取默认专辑图片
  * @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;
 }
}

⑥为列表设置adapter,新建一个MyAdapter类继承BaseAdapter,然后在重写的getView里面设置显示的控件

@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实现后台播放

使用的是bindService,这样Service的生命周期就和Activity的生命周期绑定在一起了。创建一个MusicService。注意:销毁Service的时候需要将音乐对象release。

①Service实现功能,在onBind方法里面实例化音乐播放对象

@Override
public IBinder onBind(Intent intent) {
 Log.d(TAG,"onBind is call");
 myBinder=new MyBinder();
 return myBinder;
}

②在MyBinder()里面实现音乐的各种功能,使用的是内部类,初始化部分请看源代码包

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();
  }
 }
 //关闭音乐
 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();
 }
 //获取当前播放位置
 public int getPlayPosition(){
  return mediaPlayer.getCurrentPosition();
 }
 //移动到当前点播放
 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){//时间不够了自动触发下一首
   runOnUiThread(new Runnable() {//使用ui线程来触发按键点击事件,不知道这样有没有什么危害
    @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跳转到MainActivity,需要在配置文件的activity android:name=".MainActivity"
android:launchMode=“singleTask”,设置为单任务。
布局在源代码包里,在Api26级以上需要使用NotificationChannel
①设置通知所触发的PandingIntent,通过Action识别,action为自己定义的常量,setSound无声音。通过RemoteViews去实现通知栏组件的按钮实现

//设置通知
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);
 //关闭
 Intent closeIntent=new Intent(BUTTON_CLOSE_ID);
 PendingIntent closePendingIntent=PendingIntent.getBroadcast(this,0,closeIntent,0);
 remoteViews.setOnClickPendingIntent(R.id.widget_close,closePendingIntent);
}

②动态注册广播

//注册广播
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里面点击上一首或者下一首都需要调用这个方法,刷新通知栏的标题,以及状态专辑

//展示通知
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);
}

④通知栏动作接收,使用的是内部类

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);之前
设置:

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.设置歌曲选中后的样式

①在res目录下的drawable资源下新建一个类型为selector的xml文件,里面设置属性

<?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里面设置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);
}

注意:在使用的时候可能需要手动去设置里面打开权限
代码包里面的Music_Player\app\release下的MusicPlayer.apk是app安装包哦,期待您的点赞,与评论

地址:Music_Player_jb51.rar

到此这篇关于Android10.0实现本地音乐播放(附源码下载)的文章就介绍到这了,更多相关Android10.0本地音乐播放内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android音乐播放器制作 扫描本地音乐显示在手机(一)

    思路 首先是扫描本地所有的音频文件,然后全部装进集合当中,接下来就是用ListView展示在屏幕上,大概就是这几个步骤了,接下来细讲 创建一个容器 进行过数据解析的朋友都应该知道JavaBean吧,用来装载解析出来的数据,我们这里同样也要创建一个JavaBean,用来装载扫描到的音频文件,具体的代码是: package com.duanlian.mymusicplayerdemo.bean; /** * Created by user on 2016/6/24. * 放置音乐 */ public

  • 简单实现Android本地音乐播放器

    音乐播放需要调用service,在此,只是简单梳理播放流程. public class PlayMusicService extends Service { //绑定服务 调用服务的方法. @Override public IBinder onBind(Intent intent) { return null; } } <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:to

  • Android10.0实现本地音乐播放(附源码下载)

    1.概述 本篇文章仅是Android小白在写一个小程序,内容仅供参考,有很多不足之处希望各位大神指出,文章末尾有整个项目的下载,不需要币,只求帮你们解决到问题的同时收获到一颗小小的赞.这个项目中还有很多不足的地方,如:在按键中设置图片文字,这些正常的应该交给Handler处理,我只是粗略地完成这个项目.测试环境:Android10.0.实现:自动播放下一首,正常音乐的功能,全屏显示. Android10.0是内外分存了的,应用是没有权限读取内存的,需要在配置文件中application中加上属性

  • 基于vue2.0的活动倒计时组件countdown(附源码下载)

    这是一款基于vue2.0的活动倒计时组件,可以使用服务端时间作为当前时间,在倒计时开始和结束时可以自定义回调函数.   查看演示       下载源码 安装 npm install vue2-countdown --save 使用组件 首先在模板部分添加: <template> <div> <count-down v-on:start_callback="countDownS_cb(1)" v-on:end_callback="countDown

  • 基于Bootstrap漂亮简洁的CSS3价格表(附源码下载)

    这是一款漂亮简洁的CSS3价格表样式,该价格表基于Bootstrap网格系统来进行布局,通过简单的CSS3代码来美化价格表,样式非常的时尚漂亮,且能在不同屏幕下展示良好的效果. 查看演示     下载源码 HTML 首先在页面中引入bootstrap.min.css文件,这里我用官方的CDN资源,你也可以下载到本地使用. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstra

  • jQuery日期范围选择器附源码下载

    jQuery Date Range Picker是一款允许用户选择一个日期时间范围的jQuery日期选择器插件.整个日期选择器插件使用CSS来渲染样式,可以非常容易的使用CSS来定制皮肤.而且浏览器兼容性非常好,支持多种时间格式. 查看演示             下载源码 准备 使用该日期选择器插件需要 jQuery 1.3.2+和Moment 2.2.0+的支持. <link rel="stylesheet" href="css/daterangepicker.cs

  • jQuery+Ajax+PHP实现“喜欢”评级功能附源码下载

    本文章来给大家介绍一个jQuery+Ajax+PHP实现"喜欢"评级功能代码,用户点击页面中自己喜欢的图片上的红心按钮时,前端页面向后台发送一个ajax请求,后台PHP程序接收请求后,查询IP库中是否已经有该用户的点击记录,如果没有,则将对应的数值+1,同时将该用户IP信息写入IP库,反之则告诉用户已经"喜欢过了".  源码下载地址:http://xiazai.jb51.net/201509/yuanma/loveit(jb51.net).rar 实现过程 本文基于

  • C++实现四叉树效果(附源码下载)

    什么是四叉树? 如图,设想, 红框表示地图,星星表示单位,黄框表现范围, 要处理地图中范围内的单位,最直接的做法是筛选所有单位. 通过上图可以看到一个显而易见的问题,大部分单位都不需要被处理. 如果把地图分成块,只筛选范围覆盖的块中的单位,这样就可以减少很多不必要的筛选. 四叉树可以有效解决这个问题. 树的每一层都把地图划分四块,根据地图尺寸来决定树的层数,层数越大划分越细. 当需要对某一范围的单位筛选时,只需要定位到与范围相交的树区域,再对其区域内的对象筛选即可. 四叉树的实现 #pragma

  • Jquery 垂直多级手风琴菜单附源码下载

    首先给大家展示下效果图,喜欢的朋友继续往下看哦. 这是一款简单但实用的多级垂直手风琴下拉列表菜单.该多级手风琴菜单完全使用CSS来制作,它通过将不同的子菜单进行分组,制作出多级菜单的效果. 该多级手风琴菜单通过在checkbox元素上使用:checked伪元素来制作.如果需要一些好看的动画效果,可以实用插件中提供的main.js文件,它可以提供菜单打开和收缩时左侧小箭头的动画效果. 效果演示       源码下载 使用方法 HTML结构 该多级手风琴菜单的HTML结构非常简单:整个手风琴是一个无

  • 基于jQuery实现交互体验社会化分享代码附源码下载

    先给大家展示下效果图,看看是不是亲想要的效果,如果满足您的要求请继续往下阅读. 效果展示       源码下载 基于jQuery交互体验社会化分享代码.这是一款鼠标点击分享按钮向右滑出腾讯微博,新浪微博,QQ空间,豆瓣,微信,二维码分享等分享平台. html代码: <div style="text-align:center;font:normal 14px/24px 'MicroSoft YaHei';clear:both;width:160px;margin:0 auto;"&

  • jQuery和hwSlider实现内容响应式可触控滑动切换效果附源码下载(二)

    今天我们继续内容滑动切换效果的第二部分讲解.如今我们的web开发都要适应移动设备,就是说我们的web页面要在移动设备如手机端也能正常访问,所以我将第一部分的基本切换效果做了加强,增加了响应式和触控滑动效果. 效果展示    源码下载 本文是hwSlider-内容滑动切换效果的第二部分,演示DEMO都是基于第一部分内容的基础上的,所以,如果您还没阅读过第一部分的话,请先移步参阅:基于jQuery和hwSlider实现内容左右滑动切换效果附源码下载(一) 响应式 什么是响应式设计,这里我就不描述了.

  • jQuery+Ajax+PHP弹出层异步登录效果(附源码下载)

    弹出层主要用于展示丰富的页面信息,还有一个更好的应用是弹出表单层丰富交互应用.常见的应用有弹出登录表单层,用户提交登录信息,后台验证登录成功后,弹出层消失,主页面局部刷新用户信息.本文我们将给大家介绍如何使用jQuery+Ajax+PHP弹出层异步登录的应用. 效果展示        源码下载 HTML 由于本例UI部分使用了Bootstrap,以及使用jQuery插件,因此需要事先载入相关文件,推荐大家使用相关的CDN资源. <link rel="stylesheet" hre

随机推荐