HelloMoon应用介绍

HelloMoon是《Android权威编程指南》一书中的一个应用,通过这个应用,可以学习到如下知识点:

  • 通过MediaPlayer播放音频文件;
  • 使用 setRetainInstance(boolean) 方法保留fragment实例;
  • 在res资源目录下使用配置修饰符,使应用本地化。

应用只包含一个activity/fragment,点击播放,将播放一段音频,点击停止,音频将停止播放。最终效果如下所示:


这里写图片描述


本文将分为下面几个部分:

  1. fragment布局及逻辑;
  2. 封装MediaPlayer类,实现音频播放;
  3. 介绍配置修饰符的使用规则。

fragment逻辑和布局

首先为fragment设置布局:

<!-- fragment_hello_mooon.xml -->
<!-- fragment的布局 -->

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".HelloMoonFragment" >

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:contentDescription="@string/hellomoon_description"
        android:scaleType="centerInside"
        android:src="@drawable/armstrong_on_moon" />

    <TableRow
        android:layout_weight="0"
        android:gravity="center|bottom" >

        <Button
            android:id="@+id/hellomoon_playButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hellomoon_play" >
        </Button>

        <Button
            android:id="@+id/hellomoon_stopButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hellomoon_stop" />
    </TableRow>

</TableLayout>

上述XML需注意:

  • 无需为TableRow设置宽、高,宽高均由其父容器决定;
  • 设置layout_weight = “0”表示为TableRow分配的空间比重将由其子组件的大小决定。

fragment逻辑:

//HelloMoonFragment.java
//fragment的实现逻辑

public class HelloMoonFragment extends Fragment {
    private Button mPlayButton;
    private Button mStopButton;
    private AudioPlayer mAudioPlayer = new AudioPlayer();;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        // 该方法可Fragment实例一直存在(即保存了MediaPlayer实例),不会因为旋转屏幕而被销毁
        setRetainInstance(true);
    }

    @Override
    @Nullable
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub

        View view = inflater.inflate(R.layout.fragment_hello_moon, container,
                false);
        mPlayButton = (Button) view.findViewById(R.id.hellomoon_playButton);
        mPlayButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                mAudioPlayer.play(getActivity());

            }
        });
        mStopButton = (Button) view.findViewById(R.id.hellomoon_stopButton);
        mStopButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                mAudioPlayer.stop();
            }
        });
        return view;

    }

    // HelloMoonFragment被销毁后,MediaPlayer仍可不停播放,因为MediaPlayer运行在不同的线程上,故需手动停止
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        mAudioPlayer.stop();
    }

}

代码为两个按钮绑定了监听器,用于控制音频的播放和停止,同时还创建了一个AudioPlayer对象,该对象封装了用于控制音频、视频播放的类MediaPlayer。下面是AudioPlayer的逻辑实现:

public class AudioPlayer {
    private MediaPlayer mPlayer;

    public void stop() {
        if (mPlayer != null) {
            mPlayer.release();
            mPlayer = null;
        }
    }

    public void play(Context c) {
        // 避免多次单击Play按钮创建多个MediaPlayer实例的情况发生
        stop();
        mPlayer = MediaPlayer.create(c, R.raw.one_small_step);
        // 监听MediaPlayer播放情况,当播放完成后,立即释放MediaPlayer实例占用的资源
        mPlayer.setOnCompletionListener(new OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                // TODO Auto-generated method stub
                stop();

            }
        });
        mPlayer.start();
    }

}

  • MediaPlayer是独占性的:被MediaPlayer绑定了的资源将无法被其他类使用,所以,在不需要MediaPlayer的时候,要及时释放其绑定的资源;
  • 使用MediaPlayer.setOnCompletionListener(OnCompletionListener listener),为MediaPlayer绑定监听器,当资源播放完成后,OnCompletionListener 接口中的方法onCompletion(MediaPlayer mp)被回调,可在该方法中处理MediaPlayer的善后工作。
  • 当屏幕配置发生改变时(如旋转屏幕,系统语言改变等),fragment中播放的音频(MediaPlayer)将随fragment的实例被一并销毁,为了保证音频不被重置,应在Fragment.onCreate()方法中调用方法setRetainInstance(true),该方法(默认情况下参数为false)可保证当fragment的宿主activity被销毁并重建时,其托管的fragment实例不被销毁,而只是将该fragment的视图随activity销毁。在activity销毁并重建实例的时间很短,fragment没有被任何activity托管,因此setRetainInstance(true)的适用条件仅是当设备的配置发生改变时,托管的activity正在被销毁的情形。另外,如需持久地保存数据,应使用回调方法onSaveInstanceState方法,该方法用于保存并回复应用的UI状态,有关onSaveInstanceState的用法,请参见我的博文《GeoQuiz的个人见解》onSaveInstanceState与setRetainInstance方法的主要区别是数据可以保存多久,若只是短暂的保留数据,能应对设备配置的改变,使用后者就行了,因为fragment保留数据的时间就是该fragment的生命周期的时间;若需要持久地保存数据,就得用前者:当用户离开应用后,如系统因回收内存需要销毁activity,则保留的fragment也会被随之销毁。

配置修饰符的使用


不同国家和地区有自己的语言和APP使用习惯;不同设备的屏幕大小、分辨率需要使用的图片大小不同(屏幕显示密度);不同版本的设备需要为其兼容版本的主题(API级别),等等。通过配置修饰符,可以实现上面的要求,下面是具有配置修饰符的设备特征:

  1. 移动国家代码(通常附有移动网络码)
  2. 语言代码(通常附有地区代码)
  3. 布局方向
  4. 最小宽度
  5. 可用宽度
  6. 可用高度
  7. 屏幕尺寸
  8. 屏幕纵横比
  9. 屏幕方位
  10. UI模式
  11. 夜间模式
  12. 屏幕显示目睹
  13. 触摸屏类型
  14. 键盘可用性
  15. 首选输入法
  16. 导航键可用性
  17. 非文本导航方法
  18. API级别

下面是HelloMoon应用中,res目录下使用配置修饰符的情形:
这里写图片描述


  • res/raw、res/layout、res/drawable、res/values目录表示在缺省情况下的配置修饰符,例如,当系统语言切换至中文、而res目录中并没有提供values-zh目录时,应用将使用res/values中的内容;再比如,除非设备本版本等于或高于API 11,否则应用将使用默认的res/values中的资源;
  • 可以在同一资源目录上使用读个配置修饰符,各配置修饰符必须按照上述优先级的顺序排列,如res/values-zh-land;但res/values-land-zh无效;
  • 配置修饰符的匹配规则如下:
    –1、首先排除不兼容的目录——比如,在竖屏浏览应用时,res/values-land和values-zh-land目录中的内容将被忽略。特别注意的是,Android对屏幕显示密度采用了不同的处理方式,该兼容匹配规则不适用于它,Android会选择其认为最合适的资源来匹配设备配置,也就是说,系统最终选择的匹配方案可能与开发者适配的方案有出入。
    –2、按优先级筛选不兼容目录:系统会按优先级从高到低的顺序匹配修饰符,若没有为该优先级设置修饰符,则将该优先级忽略,继续按次优先级的修饰符进行筛选:当系统语言设置为中文,并将屏幕旋转至横屏时,系统会这样匹配修饰符:首先系统会寻找res目录下是否有被优先级最高的MCC修饰的目录(移动国家代码 MCC:Mobile Country Code),显然在本应用中没有,故比较次优先级的“语言代码”,由于系统语言是中文,故筛选出res/values-zh、res/values-zh-land目录,接着筛选下一个优先级“布局方向”,由于屏幕为横屏,最终筛选的结果是使用res/values-zh-land目录。当没有任何可匹配的结果被筛选出来时,系统将使用缺省的资源目录,也就是说,缺省的资源目录必须设置,否则当系统没有匹配的资源时,程序将崩溃。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package com.demo.pr5; import java.io.File; import java.util.Vector; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class MyFileActivity extends Activity { // 支持的媒体格式 private final String[]FILE_MapTable = { ".3gp",".mov",".avi", ".rmvb", ".wmv", ".mp3", ".mp4" }; private Vector<String> items = null; // items:存放显示的名称 private Vector<String> paths = null; // paths:存放文件路径 private Vector<String> sizes = null; // sizes:文件大小 private String rootPath = "/mnt/sdcard"; //起始文件夹 private EditText pathEditText; // 路径 private Button queryButton; //查询按钮 private ListView fileListView;//文件列表 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); this.setTitle("多媒体文件浏览"); setContentView(R.layout.myfile); //从myfile.xml找到对应的元素 pathEditText = (EditText) findViewById(R.id.path_edit); queryButton = (Button) findViewById(R.id.qry_button); fileListView= (ListView) findViewById(R.id.file_listview); //查询按钮事件 queryButton.setOnClickListener( new Button.OnClickListener() { public void onClick(View arg0) { File file = new File(pathEditText.getText().toString()); if (file.exists()) { if (file.isFile()) { //如果是媒体文件直接打开播放 openFile(pathEditText.getText().toString()); } else { //如果是目录打开目录下文件 getFileDir(pathEditText.getText().toString()); } } else { Toast.makeText(MyFileActivity.this, "找不到该位置,请确定位置是否正确!", Toast.LENGTH_SHORT).show(); } } }); //设置ListItem被点击时要做的动作 fileListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) { fileOrDir(paths.get(position)); } }); //打开默认文件夹 getFileDir(rootPath); } /** * 重写返回键功能:返回上一级文件夹 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 是否触发按键为back键 if (keyCode == KeyEvent.KEYCODE_BACK) { pathEditText = (EditText) findViewById(R.id.path_edit); File file = new File(pathEditText.getText().toString()); if (rootPath.equals(pathEditText.getText().toString().trim())) { return super.onKeyDown(keyCode, event); } else { getFileDir(file.getParent()); return true; } //如果不是back键正常响应 } else { return super.onKeyDown(keyCode, event); } } /** * 处理文件或者目录的方法 */ private void fileOrDir(String path) { File file = new File(path); if (file.isDirectory()) { getFileDir(file.getPath()); } else { openFile(path); } } /** * 取得文件结构的方法 */ private void getFileDir(String filePath) { /* 设置目前所在路径 */ pathEditText.setText(filePath); items = new Vector<String>(); paths = new Vector<String>(); sizes = new Vector<String>(); File f = new File(filePath); File[] files = f.listFiles(); if (files != null) { /* 将所有文件添加ArrayList */ for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { items.add(files[i].getName()); paths.add(files[i].getPath()); sizes.add(""); } } for (int i = 0; i < files.length; i++) { if (files[i].isFile()) { String fileName = files[i].getName(); int index = fileName.lastIndexOf("."); if (index > 0) { String endName = fileName.substring(index, fileName.length()).toLowerCase(); String type = null; for (int x = 0; x < FILE_MapTable.length; x++) { // 支持的格式,才会在文件浏览器显示 if (endName.equals(FILE_MapTable[x])) { type = FILE_MapTable[x]; break; } } if (type != null) { items.add(files[i].getName()); paths.add(files[i].getPath()); sizes.add(files[i].length()+""); } } } } } /* 使用自定义的FileListAdapter来将数据传入ListView */ fileListView.setAdapter(new FileListAdapter(this, items)); } /** * 打开媒体文件 * @param f */ private void openFile(String path) { //打开媒体播放器 Intent intent = new Intent(MyFileActivity.this, MediaPlayerActivity.class); intent.putExtra("path",path); startActivity(intent); finish(); } /** *ListView列表适配器 */ class FileListAdapter extends BaseAdapter { private Vector<String> items = null; // items:存放显示的名称 private MyFileActivity myFile; public FileListAdapter(MyFileActivity myFile, Vector<String> items) { this.items=items; this.myFile=myFile; } @Override public int getCount() { // TODO Auto-generated method stub return items.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return items.elementAt(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return items.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub if(convertView==null) { //加载列表项布局file_item.xml convertView = myFile.getLayoutInflater() .inflate(R.layout.file_item, null); } //文件名称 TextView name = (TextView) convertView.findViewById(R.id.name); //媒体文件类型 ImageView music=(ImageView)convertView.findViewById(R.id.music); //文件夹类型 ImageView folder=(ImageView)convertView.findViewById(R.id.folder); name.setText(items.elementAt(position)); if(sizes.elementAt(position).equals("")) { //隐藏媒体图标,显示文件夹图标 music.setVisibility(View.GONE); folder.setVisibility(View.VISIBLE); }else { //隐藏文件夹图标,显示媒体图标 folder.setVisibility(View.GONE); music.setVisibility(View.VISIBLE); } return convertView; } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值