• Android 歌词同步滚动效果(转)


    歌词是播放器类App必不可少的组件,而一般的歌词组件都需要做到歌词的显示与播放进度同步。我们知道,歌词是如下所示的文件:

    lrc 
    [ti:原来爱情这么伤]
    [ar:梁咏琪]
    [al:给自己的情歌]


    [00:00.55]梁咏琪 - 原来爱情这么伤
    [00:05.43]作词:彭学斌
    [00:06.68]作曲:彭学斌
    [00:09.63]
    [00:22.27]我睁开眼睛 却感觉不到天亮
    [00:29.74]东西吃一半 莫名其妙哭一场
    [00:37.06]我忍住不想 时间变得更漫长
    [00:44.09]也与你有关 否则又开始胡思乱想
    [00:53.81]我日月无光 忙得不知所以然
    [00:59.96]找朋友交谈 其实全帮不上忙
    [01:07.49]以为会习惯 有你在才是习惯
    [01:14.62]你曾住在我心上 现在空了一个地方
    [01:21.89]原来爱情这么伤 比想象中还难
    [01:29.90]泪水总是不听话 幸福躲起来不声不响
    [01:37.43]太多道理太牵强 道理全是一样
    [01:44.34]说的时候很简单 爱上后却正巧打乱
    [02:00.00]我日月无光 忙得不知所以然
    [02:07.41]找朋友交谈 其实全帮不上忙
    [02:15.07]以为会习惯 有你在才是习惯
    [02:21.88]你曾住在我心上 现在空了一个地方
    [02:29.38]原来爱情这么伤 比想象中还难
    [02:36.60]泪水总是不听话 幸福躲起来不声不响
    [02:44.22]太多道理太牵强 道理全是一样
    [02:50.78]说的时候很简单 爱上后却正巧打乱
    [03:00.32]只想变的坚强 强到能够去忘
    [03:07.29]无所谓悲伤 只要学会抵抗
    [03:14.19]原来爱情这么伤
    [03:20.78]原来爱情是这样 这样峰回路转
    [03:28.12]泪水明明流不干 瞎了眼还要再爱一趟
    [03:35.83]有一天终于打完 思念的一场战
    [03:43.45]回过头再看一看 原来爱情那么伤
    [03:54.76]下次还会不会这样
    [88:88.88]

    我们需要读取以上歌词文件的每一行转换成成一个个歌词实体:

    Java代码  收藏代码
    1. package com.music.lyricsync;  
    2.   
    3. public class LyricObject {  
    4.     public int begintime; // 开始时间  
    5.     public int endtime; // 结束时间  
    6.     public int timeline; // 单句歌词用时  
    7.     public String lrc; // 单句歌词  
    8. }  

    可根据当前播放器的播放进度与每句歌词的开始时间,得到当前屏幕中央高亮显示的那句歌词。在UI线程中另起线程,通过回调函数 onDraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。MainActivity代码如下:

    Java代码  收藏代码
    1. package com.music.lyricsync;  
    2.   
    3. import java.io.IOException;  
    4. import android.app.Activity;  
    5. import android.media.MediaPlayer;  
    6. import android.net.Uri;  
    7. import android.os.Bundle;  
    8. import android.os.Environment;  
    9. import android.os.Handler;  
    10. import android.view.View;  
    11. import android.view.View.OnClickListener;  
    12. import android.widget.Button;  
    13. import android.widget.SeekBar;  
    14. import android.widget.SeekBar.OnSeekBarChangeListener;  
    15.   
    16. public class MainActivity extends Activity {  
    17.     /** Called when the activity is first created. */  
    18.     private LyricView lyricView;  
    19.     private MediaPlayer mediaPlayer;  
    20.     private Button button;  
    21.     private SeekBar seekBar;  
    22.     private String mp3Path;  
    23.     private int INTERVAL=45;//歌词每行的间隔  
    24.   
    25.     @Override  
    26.     public void onCreate(Bundle savedInstanceState) {  
    27.         super.onCreate(savedInstanceState);  
    28.         // this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
    29.         // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);  
    30.         setContentView(R.layout.main);  
    31.   
    32.         mp3Path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/LyricSync/1.mp3";  
    33.   
    34.         lyricView = (LyricView) findViewById(R.id.mylrc);  
    35.         mediaPlayer = new MediaPlayer();  
    36.         // this.requestWindowFeature(Window.FEATURE_NO_TITLE);  
    37.   
    38.         ResetMusic(mp3Path);  
    39.         SerchLrc();  
    40.         lyricView.SetTextSize();  
    41.   
    42.         button = (Button) findViewById(R.id.button);  
    43.         button.setText("播放");  
    44.   
    45.         seekBar = (SeekBar) findViewById(R.id.seekbarmusic);  
    46.         seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {  
    47.   
    48.             @Override  
    49.             public void onStopTrackingTouch(SeekBar seekBar) {  
    50.                 // TODO Auto-generated method stub  
    51.   
    52.             }  
    53.   
    54.             @Override  
    55.             public void onStartTrackingTouch(SeekBar seekBar) {  
    56.                 // TODO Auto-generated method stub  
    57.   
    58.             }  
    59.   
    60.             @Override  
    61.             public void onProgressChanged(SeekBar seekBar, int progress,  
    62.                     boolean fromUser) {  
    63.                 // TODO Auto-generated method stub  
    64.                 if (fromUser) {  
    65.                     mediaPlayer.seekTo(progress);  
    66.                     lyricView.setOffsetY(220 - lyricView.SelectIndex(progress)   
    67.                             * (lyricView.getSIZEWORD() + INTERVAL-1));  
    68.   
    69.                 }  
    70.             }  
    71.         });  
    72.   
    73.         button.setOnClickListener(new OnClickListener() {  
    74.   
    75.             @Override  
    76.             public void onClick(View v) {  
    77.                 // TODO Auto-generated method stub  
    78.                 if (mediaPlayer.isPlaying()) {  
    79.                     button.setText("播放");  
    80.                     mediaPlayer.pause();  
    81.                 } else {  
    82.                     button.setText("暂停");  
    83.                     mediaPlayer.start();  
    84.                     lyricView.setOffsetY(220 - lyricView.SelectIndex(mediaPlayer.getCurrentPosition())  
    85.                             * (lyricView.getSIZEWORD() + INTERVAL-1));  
    86.   
    87.                 }  
    88.             }  
    89.         });  
    90.   
    91.         mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {  
    92.             @Override  
    93.             public void onCompletion(MediaPlayer mp) {  
    94.                 ResetMusic(mp3Path);  
    95.                 lyricView.SetTextSize();  
    96.                 lyricView.setOffsetY(200);  
    97.                 mediaPlayer.start();  
    98.             }  
    99.         });  
    100.         seekBar.setMax(mediaPlayer.getDuration());  
    101.         new Thread(new runable()).start();  
    102.     }  
    103.   
    104.     public void SerchLrc() {  
    105.         String lrc = mp3Path;  
    106.         lrc = lrc.substring(0, lrc.length() - 4).trim() + ".lrc".trim();  
    107.         LyricView.read(lrc);  
    108.         lyricView.SetTextSize();  
    109.         lyricView.setOffsetY(350);  
    110.     }  
    111.   
    112.     public void ResetMusic(String path) {  
    113.   
    114.         mediaPlayer.reset();  
    115.         try {  
    116.   
    117.             mediaPlayer.setDataSource(mp3Path);  
    118.             mediaPlayer.prepare();  
    119.         } catch (IllegalArgumentException e) {  
    120.             // TODO Auto-generated catch block  
    121.             e.printStackTrace();  
    122.         } catch (IllegalStateException e) {  
    123.             // TODO Auto-generated catch block  
    124.             e.printStackTrace();  
    125.         } catch (IOException e) {  
    126.             // TODO Auto-generated catch block  
    127.             e.printStackTrace();  
    128.         }  
    129.     }  
    130.   
    131.     class runable implements Runnable {  
    132.   
    133.         @Override  
    134.         public void run() {  
    135.             // TODO Auto-generated method stub  
    136.             while (true) {  
    137.   
    138.                 try {  
    139.                     Thread.sleep(100);  
    140.                     if (mediaPlayer.isPlaying()) {  
    141.                         lyricView.setOffsetY(lyricView.getOffsetY() - lyricView.SpeedLrc());  
    142.                         lyricView.SelectIndex(mediaPlayer.getCurrentPosition());  
    143.                         seekBar.setProgress(mediaPlayer.getCurrentPosition());  
    144.                         mHandler.post(mUpdateResults);  
    145.                     }  
    146.                 } catch (InterruptedException e) {  
    147.                     // TODO Auto-generated catch block  
    148.                     e.printStackTrace();  
    149.                 }  
    150.             }  
    151.         }  
    152.     }  
    153.   
    154.     Handler mHandler = new Handler();  
    155.     Runnable mUpdateResults = new Runnable() {  
    156.         public void run() {  
    157.             lyricView.invalidate(); // 更新视图  
    158.         }  
    159.     };  
    160. }  

    歌词View的代码如下:

    Java代码  收藏代码
    1. package com.music.lyricsync;  
    2.   
    3. import java.io.BufferedReader;  
    4. import java.io.File;  
    5. import java.io.FileInputStream;  
    6. import java.io.FileNotFoundException;  
    7. import java.io.IOException;  
    8. import java.io.InputStreamReader;  
    9. import java.util.Iterator;  
    10. import java.util.TreeMap;  
    11. import java.util.regex.Matcher;  
    12. import java.util.regex.Pattern;  
    13.   
    14. import android.content.Context;  
    15. import android.graphics.Canvas;  
    16. import android.graphics.Color;  
    17. import android.graphics.Paint;  
    18. import android.util.AttributeSet;  
    19. import android.util.Log;  
    20. import android.view.MotionEvent;  
    21. import android.view.View;  
    22.   
    23. public class LyricView extends View{  
    24.       
    25.     private static TreeMap<Integer, LyricObject> lrc_map;  
    26.     private float mX;       //屏幕X轴的中点,此值固定,保持歌词在X中间显示  
    27.     private float offsetY;      //歌词在Y轴上的偏移量,此值会根据歌词的滚动变小  
    28.     private static boolean blLrc=false;  
    29.     private float touchY;   //当触摸歌词View时,保存为当前触点的Y轴坐标  
    30.     private float touchX;  
    31.     private boolean blScrollView=false;  
    32.     private int lrcIndex=0//保存歌词TreeMap的下标  
    33.     private  int SIZEWORD=0;//显示歌词文字的大小值  
    34.     private  int INTERVAL=45;//歌词每行的间隔  
    35.     Paint paint=new Paint();//画笔,用于画不是高亮的歌词  
    36.     Paint paintHL=new Paint();  //画笔,用于画高亮的歌词,即当前唱到这句歌词  
    37.       
    38.     public LyricView(Context context){  
    39.         super(context);  
    40.         init();  
    41.     }  
    42.       
    43.     public LyricView(Context context, AttributeSet attrs) {  
    44.         super(context, attrs);  
    45.         init();  
    46.     }  
    47.       
    48.     /* (non-Javadoc) 
    49.      * @see android.view.View#onDraw(android.graphics.Canvas) 
    50.      */  
    51.     @Override  
    52.     protected void onDraw(Canvas canvas) {  
    53.         if(blLrc){  
    54.             paintHL.setTextSize(SIZEWORD);  
    55.             paint.setTextSize(SIZEWORD);  
    56.             LyricObject temp=lrc_map.get(lrcIndex);  
    57.             canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*lrcIndex, paintHL);  
    58.             // 画当前歌词之前的歌词  
    59.             for(int i=lrcIndex-1;i>=0;i--){  
    60.                 temp=lrc_map.get(i);  
    61.                 if(offsetY+(SIZEWORD+INTERVAL)*i<0){  
    62.                     break;  
    63.                 }  
    64.                 canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint);  
    65.             }  
    66.             // 画当前歌词之后的歌词  
    67.             for(int i=lrcIndex+1;i<lrc_map.size();i++){  
    68.                 temp=lrc_map.get(i);  
    69.                 if(offsetY+(SIZEWORD+INTERVAL)*i>600){  
    70.                     break;  
    71.                 }  
    72.                 canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint);  
    73.             }  
    74.         }  
    75.         else{  
    76.             paint.setTextSize(25);  
    77.             canvas.drawText("找不到歌词", mX, 310, paint);  
    78.         }  
    79.         super.onDraw(canvas);  
    80.     }  
    81.   
    82.     /* (non-Javadoc) 
    83.      * @see android.view.View#onTouchEvent(android.view.MotionEvent) 
    84.      */  
    85.     @Override  
    86.     public boolean onTouchEvent(MotionEvent event) {  
    87.         // TODO Auto-generated method stub  
    88.         System.out.println("bllll==="+blScrollView);  
    89.         float tt=event.getY();  
    90.         if(!blLrc){  
    91.             //return super.onTouchEvent(event);  
    92.   
    93.             return super.onTouchEvent(event);  
    94.         }  
    95.         switch(event.getAction()){  
    96.         case MotionEvent.ACTION_DOWN:  
    97.             touchX=event.getX();  
    98.             break;  
    99.         case MotionEvent.ACTION_MOVE:  
    100.             touchY=tt-touchY;             
    101.             offsetY=offsetY+touchY;  
    102.             break;  
    103.         case MotionEvent.ACTION_UP:  
    104.             blScrollView=false;  
    105.             break;        
    106.         }  
    107.         touchY=tt;  
    108.         return true;  
    109.     }  
    110.   
    111.     public void init(){  
    112.         lrc_map = new TreeMap<Integer, LyricObject>();  
    113.         offsetY=320;      
    114.           
    115.         paint=new Paint();  
    116.         paint.setTextAlign(Paint.Align.CENTER);  
    117.         paint.setColor(Color.GREEN);  
    118.         paint.setAntiAlias(true);  
    119.         paint.setDither(true);  
    120.         paint.setAlpha(180);  
    121.           
    122.           
    123.         paintHL=new Paint();  
    124.         paintHL.setTextAlign(Paint.Align.CENTER);  
    125.           
    126.         paintHL.setColor(Color.RED);  
    127.         paintHL.setAntiAlias(true);  
    128.         paintHL.setAlpha(255);  
    129.     }  
    130.       
    131.     /** 
    132.      * 根据歌词里面最长的那句来确定歌词字体的大小 
    133.      */  
    134.       
    135.     public void SetTextSize(){  
    136.         if(!blLrc){  
    137.             return;  
    138.         }  
    139.         int max=lrc_map.get(0).lrc.length();  
    140.         for(int i=1;i<lrc_map.size();i++){  
    141.             LyricObject lrcStrLength=lrc_map.get(i);  
    142.             if(max<lrcStrLength.lrc.length()){  
    143.                 max=lrcStrLength.lrc.length();  
    144.             }  
    145.         }  
    146.         SIZEWORD=320/max;  
    147.       
    148.     }  
    149.       
    150.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
    151.         mX = w * 0.5f;  
    152.         super.onSizeChanged(w, h, oldw, oldh);  
    153.     }  
    154.       
    155.     /** 
    156.      *  歌词滚动的速度 
    157.      *  
    158.      * @return 返回歌词滚动的速度 
    159.      */  
    160.     public Float SpeedLrc(){  
    161.         float speed=0;  
    162.         if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex>220){  
    163.             speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20);  
    164.   
    165.         } else if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex < 120){  
    166.             Log.i("speed""speed is too fast!!!");  
    167.             speed = 0;  
    168.         }  
    169. //      if(speed<0.2){  
    170. //          speed=0.2f;  
    171. //      }  
    172.         return speed;  
    173.     }  
    174.       
    175.     /** 
    176.      * 按当前的歌曲的播放时间,从歌词里面获得那一句 
    177.      * @param time 当前歌曲的播放时间 
    178.      * @return 返回当前歌词的索引值 
    179.      */  
    180.     public int SelectIndex(int time){  
    181.         if(!blLrc){  
    182.             return 0;  
    183.         }  
    184.         int index=0;  
    185.         for(int i=0;i<lrc_map.size();i++){  
    186.             LyricObject temp=lrc_map.get(i);  
    187.             if(temp.begintime<time){  
    188.                 ++index;  
    189.             }  
    190.         }  
    191.         lrcIndex=index-1;  
    192.         if(lrcIndex<0){  
    193.             lrcIndex=0;  
    194.         }  
    195.         return lrcIndex;  
    196.       
    197.     }  
    198.       
    199.     /** 
    200.      * 读取歌词文件 
    201.      * @param file 歌词的路径 
    202.      *  
    203.      */  
    204.     public static void read(String file) {  
    205.         TreeMap<Integer, LyricObject> lrc_read =new TreeMap<Integer, LyricObject>();  
    206.         String data = "";  
    207.         try {  
    208.           File saveFile=new File(file);  
    209.          // System.out.println("是否有歌词文件"+saveFile.isFile());  
    210.           if(!saveFile.isFile()){  
    211.               blLrc=false;  
    212.               return;  
    213.           }  
    214.           blLrc=true;  
    215.             
    216.           //System.out.println("bllrc==="+blLrc);  
    217.           FileInputStream stream = new FileInputStream(saveFile);//  context.openFileInput(file);  
    218.             
    219.             
    220.           BufferedReader br = new BufferedReader(new InputStreamReader(stream,"GB2312"));     
    221.           int i = 0;  
    222.           Pattern pattern = Pattern.compile("\d{2}");  
    223.           while ((data = br.readLine()) != null) {     
    224.              // System.out.println("++++++++++++>>"+data);  
    225.                 data = data.replace("[","");//将前面的替换成后面的  
    226.                 data = data.replace("]","@");  
    227.                 String splitdata[] =data.split("@");//分隔  
    228.                 if(data.endsWith("@")){  
    229.                     for(int k=0;k<splitdata.length;k++){  
    230.                         String str=splitdata[k];  
    231.                           
    232.                         str = str.replace(":",".");  
    233.                         str = str.replace(".","@");  
    234.                         String timedata[] =str.split("@");  
    235.                         Matcher matcher = pattern.matcher(timedata[0]);  
    236.                         if(timedata.length==3 && matcher.matches()){  
    237.                             int m = Integer.parseInt(timedata[0]);  //分  
    238.                             int s = Integer.parseInt(timedata[1]);  //秒  
    239.                             int ms = Integer.parseInt(timedata[2]); //毫秒  
    240.                             int currTime = (m*60+s)*1000+ms*10;  
    241.                             LyricObject item1= new LyricObject();  
    242.                             item1.begintime = currTime;  
    243.                             item1.lrc       = "";  
    244.                             lrc_read.put(currTime,item1);  
    245.                         }  
    246.                     }  
    247.                       
    248.                 }  
    249.                 else{  
    250.                     String lrcContenet = splitdata[splitdata.length-1];   
    251.               
    252.                     for (int j=0;j<splitdata.length-1;j++)  
    253.                     {  
    254.                         String tmpstr = splitdata[j];  
    255.                           
    256.                         tmpstr = tmpstr.replace(":",".");  
    257.                         tmpstr = tmpstr.replace(".","@");  
    258.                         String timedata[] =tmpstr.split("@");  
    259.                         Matcher matcher = pattern.matcher(timedata[0]);  
    260.                         if(timedata.length==3 && matcher.matches()){  
    261.                             int m = Integer.parseInt(timedata[0]);  //分  
    262.                             int s = Integer.parseInt(timedata[1]);  //秒  
    263.                             int ms = Integer.parseInt(timedata[2]); //毫秒  
    264.                             int currTime = (m*60+s)*1000+ms*10;  
    265.                             LyricObject item1= new LyricObject();  
    266.                             item1.begintime = currTime;  
    267.                             item1.lrc       = lrcContenet;  
    268.                             lrc_read.put(currTime,item1);// 将currTime当标签  item1当数据 插入TreeMap里  
    269.                             i++;  
    270.                         }  
    271.                     }  
    272.                 }  
    273.                   
    274.           }   
    275.          stream.close();  
    276.         }  
    277.         catch (FileNotFoundException e) {  
    278.         }  
    279.         catch (IOException e) {  
    280.         }  
    281.           
    282.         /* 
    283.          * 遍历hashmap 计算每句歌词所需要的时间 
    284.         */  
    285.         lrc_map.clear();  
    286.         data ="";  
    287.         Iterator<Integer> iterator = lrc_read.keySet().iterator();  
    288.         LyricObject oldval  = null;  
    289.         int i =0;  
    290.         while(iterator.hasNext()) {  
    291.             Object ob =iterator.next();  
    292.               
    293.             LyricObject val = (LyricObject)lrc_read.get(ob);  
    294.               
    295.             if (oldval==null)  
    296.                 oldval = val;  
    297.             else  
    298.             {  
    299.                 LyricObject item1= new LyricObject();  
    300.                 item1  = oldval;  
    301.                 item1.timeline = val.begintime-oldval.begintime;  
    302.                 lrc_map.put(new Integer(i), item1);  
    303.                 i++;  
    304.                 oldval = val;  
    305.             }  
    306.             if (!iterator.hasNext()) {  
    307.                 lrc_map.put(new Integer(i), val);  
    308.             }  
    309.               
    310.         }  
    311.   
    312.     }     
    313.       
    314.     /** 
    315.      * @return the blLrc 
    316.      */  
    317.     public static boolean isBlLrc() {  
    318.         return blLrc;  
    319.     }  
    320.   
    321.     /** 
    322.      * @return the offsetY 
    323.      */  
    324.     public float getOffsetY() {  
    325.         return offsetY;  
    326.     }  
    327.   
    328.     /** 
    329.      * @param offsetY the offsetY to set 
    330.      */  
    331.     public void setOffsetY(float offsetY) {  
    332.         this.offsetY = offsetY;  
    333.     }  
    334.   
    335.     /** 
    336.      * @return 返回歌词文字的大小 
    337.      */  
    338.     public int getSIZEWORD() {  
    339.         return SIZEWORD;  
    340.     }  
    341.   
    342.     /** 
    343.      * 设置歌词文字的大小 
    344.      * @param sIZEWORD the sIZEWORD to set 
    345.      */  
    346.     public void setSIZEWORD(int sIZEWORD) {  
    347.         SIZEWORD = sIZEWORD;  
    348.     }  
    349. }  

     

    xml布局文件如下:

    Xml代码  收藏代码
    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:layout_width="fill_parent"  
    4.     android:layout_height="fill_parent"  
    5.     android:background="#FFFFFF" >  
    6.   
    7.     <com.music.lyricsync.LyricView  
    8.         android:id="@+id/mylrc"  
    9.         android:layout_width="fill_parent"  
    10.         android:layout_height="fill_parent"  
    11.         android:layout_marginBottom="50dip"  
    12.         android:layout_marginTop="50dip" />  
    13.   
    14.     <LinearLayout  
    15.         xmlns:android="http://schemas.android.com/apk/res/android"  
    16.         android:layout_width="wrap_content"  
    17.         android:layout_height="wrap_content"  
    18.         android:layout_alignParentBottom="true"  
    19.         android:orientation="horizontal" >  
    20.   
    21.         <Button  
    22.             android:id="@+id/button"  
    23.             android:layout_width="wrap_content"  
    24.             android:layout_height="wrap_content" />  
    25.   
    26.         <SeekBar  
    27.             android:id="@+id/seekbarmusic"  
    28.             android:layout_width="205px"  
    29.             android:layout_height="wrap_content"  
    30.             android:layout_gravity="center_vertical"  
    31.             android:layout_marginBottom="5px"  
    32.             android:progress="0" />  
    33.     </LinearLayout>  
    34.   
    35. </RelativeLayout>  

     

    程序运行后如下图所示:


    运行程序前,先在SDCard根目录下新建LyricSync目录,将 歌曲和歌词.zip 中的 1.mp3 和 1.lrc 文件解压放入LyricSync目录下即可。

    • src.zip (179.2 KB)
    • 下载次数: 186
  • 相关阅读:
    LaTeX不能识别eps文件?
    ubuntu 11.04系统清理(不断更新。。。)
    换Ubuntu邮件客户端Evolution为Thunderbird
    Byobu:打造多任务的Terminal
    Learning the Vi Editor, 6th Edition学习笔记(1)
    Ubuntu:让桌面显示回收站
    3rd Party Repository for Dropbox
    ubuntu 显示隐藏文件
    Ubuntu下的一款Dock工具AWN
    i686和x86_64的区别
  • 原文地址:https://www.cnblogs.com/weixiao870428/p/3570940.html
Copyright © 2020-2023  润新知