Android开源项目-Jamendo音乐播放器研究与优化-手势操作

【五线谱上确定音的高低的记号叫做谱号,谱号有三种:G谱号(高音谱号)、F谱号(低音谱号)和C谱号(中音谱号)】

 --- 《五线谱基础教程》

 

Jamendo的手势操作用于控制歌曲的播放,有四个手势分别对应:播放,暂停,上一首和下一首。本文主要讲解手势操作部分的实现,至于涉及到媒体播放器的部分,将不作细讲,放在后续文章中。

 

一 Android手势操作API

Android提供GestureLibrary代表手势库,同时提供工具类GestureLibraries从不同数据源来创建手势库。其中GestureLibrary是一个抽象类,它的子类有两个,分别是FileGestureLibrary和ResourceGestureLibrary,从名字可以看出这两个手势库的数据来源,分别是文件和Android raw资源。这两个类作为GestureLibraries类的私有内部类实现。

GestureLibrary类实际上是对另一个类即GestureStore的一层封装,下面来看下GestureLibrary的代码:

public abstract class GestureLibrary {
    protected final GestureStore mStore;

    protected GestureLibrary() {
        mStore = new GestureStore();
    }

  // 保存手势库
    public abstract boolean save();

  // 加载手势库
    public abstract boolean load();

    public boolean isReadOnly() {
        return false;
    }

    /** @hide */
    public Learner getLearner() {
        return mStore.getLearner();
    }

    public void setOrientationStyle(int style) {
        mStore.setOrientationStyle(style);
    }

    public int getOrientationStyle() {
        return mStore.getOrientationStyle();
    }

    public void setSequenceType(int type) {
        mStore.setSequenceType(type);
    }

    public int getSequenceType() {
        return mStore.getSequenceType();
    }

    public Set getGestureEntries() {
        return mStore.getGestureEntries();
    }

    public ArrayList recognize(Gesture gesture) {
        return mStore.recognize(gesture);
    }

    public void addGesture(String entryName, Gesture gesture) {
        mStore.addGesture(entryName, gesture);
    }

    public void removeGesture(String entryName, Gesture gesture) {
        mStore.removeGesture(entryName, gesture);
    }

    public void removeEntry(String entryName) {
        mStore.removeEntry(entryName);
    }

    public ArrayList getGestures(String entryName) {
        return mStore.getGestures(entryName);
    }
}

两个子类主要实现了父类的两个抽象函数,以及重写了isReadOnly函数,分别对应不同的数据来源,但最终都是将数据转换成流的形式传给GestureStore实例的函数,由它完成最终的操作,下面看代码就清楚了:

    // 从File数据源加载手势库的类
    private static class FileGestureLibrary extends GestureLibrary {
        private final File mPath;

        public FileGestureLibrary(File path) {
            mPath = path;
        }

        @Override
        public boolean isReadOnly() {
            return !mPath.canWrite();
        }

        public boolean save() {
  // 手势库没有发生变化,就不需要重新保存
            if (!mStore.hasChanged()) return true;

            final File file = mPath;

            final File parentFile = file.getParentFile();
  // 第一次保存时,保证文件路径的存在
            if (!parentFile.exists()) {
                if (!parentFile.mkdirs()) {
                    return false;
                }
            }

            boolean result = false;
            try {
                file.createNewFile();
                mStore.save(new FileOutputStream(file), true); // 实际的保存操作
                result = true;
            } catch (FileNotFoundException e) {
                Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
            } catch (IOException e) {
                Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
            }

            return result;
        }

        public boolean load() {
            boolean result = false;
            final File file = mPath;
            if (file.exists() && file.canRead()) {
                try {
                    // 调用GestureStore实例的函数加载手势库文件
                    mStore.load(new FileInputStream(file), true);
                    result = true;
                } catch (FileNotFoundException e) {
                    Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
                } catch (IOException e) {
                    Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
                }
            }

            return result;
        }
  }
  
  // 从Android的res/raw目录加载对应的手势库文件
    private static class ResourceGestureLibrary extends GestureLibrary {
        private final WeakReference mContext;
        private final int mResourceId;

        public ResourceGestureLibrary(Context context, int resourceId) {
            mContext = new WeakReference(context);
            mResourceId = resourceId;
        }

        @Override
        public boolean isReadOnly() { // raw目录下的手势库文件是只读的
            return true;
        }

        // 这种数据源只能加载不能保存
        public boolean save() {
            return false;
        }

        public boolean load() {
            boolean result = false;
            final Context context = mContext.get();
            if (context != null) {
                final InputStream in = context.getResources().openRawResource(mResourceId);
                try {
                    mStore.load(in, true);
                    result = true;
                } catch (IOException e) {
                    Log.d(LOG_TAG, "Could not load the gesture library from raw resource " +
                            context.getResources().getResourceName(mResourceId), e);
                }
            }

            return result;
        }
  }

Android手势API除了以上两个类外,还有一个继承自FrameLayout的GestureOverlayView类,用户可以方便地在该组件上面绘制各种手势。由于GestureOverlayView不是标准的视图组件,因此在xml布局文件中使用时,需要指定全限定的类名,如下所示:

其中android:gestureStrokeType属性用于控制手势是一笔完成还是需要多笔完成,single表示单笔完成,multiple表示多笔完成。

GestureOverlayView中定义了三个监听器接口,方便用户在手势操作的各个阶段进行响应,它们定义如下:

  public static interface OnGesturingListener {
        void onGesturingStarted(GestureOverlayView overlay);
        void onGesturingEnded(GestureOverlayView overlay);
    }

    public static interface OnGestureListener {
        void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
        void onGesture(GestureOverlayView overlay, MotionEvent event);
        void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
        void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
    }

    public static interface OnGesturePerformedListener {
        void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
  }

想要知道上面各个接口函数的实际意义,还得从GestureOverlayView源码入手,用户在View上的触摸事件可分为三步,依次是MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE和MotionEvent.ACTION_UP,在GestureOverlayView类中相应的处理函数分别是touchDown、touchMove和touchUp。

 

1)在touchDown函数中可以看到如下一段代码:

        final ArrayList listeners = mOnGestureListeners;
        final int count = listeners.size();
        for (int i = 0; i < count; i++) {
            listeners.get(i).onGestureStarted(this, event);
        }

这里发现我们的onGestureStarted接口函数,因此,它是第一个被调用在,即在用户按下的时候。

 

2)在touchMove函数中的代码,首先发现onGesturingStarted函数:

                        final ArrayList listeners = mOnGesturingListeners;
                        int count = listeners.size();
                        for (int i = 0; i < count; i++) {
                            listeners.get(i).onGesturingStarted(this);
                        }

在函数最后发现了onGesture函数的回调代码代码如下:

            final ArrayList listeners = mOnGestureListeners;
            final int count = listeners.size();
            for (int i = 0; i < count; i++) {
                listeners.get(i).onGesture(this, event);
            }

 

3)最后在touchUp函数中依次发现onGestureEnded和onGesturingEnded被调用:

        final ArrayList listeners = mOnGestureListeners;
        int count = listeners.size();
        for (int i = 0; i < count; i++) {
            listeners.get(i).onGestureEnded(this, event);
        }

        clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
                 false);
...
        final ArrayList listeners = mOnGesturingListeners;
        int count = listeners.size();
        for (int i = 0; i < count; i++) {
            listeners.get(i).onGesturingEnded(this);
        }

这样一来,已经发现的接口函数调用顺序依次是onGestureStarted、onGesturingStarted、onGesture、onGestureEnded和onGesturingEnded,还差onGestureCancelled和onGesturePerformed两个。首先来查找onGestureCancelled函数,它在私有函数cancelGesture中被调用,代码跟其他接口函数类似:

    private void cancelGesture(MotionEvent event) {
        // pass the event to handlers
        final ArrayList listeners = mOnGestureListeners;
        final int count = listeners.size();
        for (int i = 0; i < count; i++) {
            listeners.get(i).onGestureCancelled(this, event);
        }

        clear(false);
  }

而这个私有函数则只在touchUp中被调用,而且onGestureCancel和onGestureEnded是二选一的关系,因为当手势操作被取消时,就自然不存在onGestureEnded的情况了。但是onGesturingEnded函数会紧随onGestureCancel被调用。

最后的接口函数onGesturePerformed被内部类FadeOutRunnable间接调用,而这个Runnable类是在GestureOverlayView类的clear函数中使用postDelayed以异步延迟消息的形式发送到消息程序的消息队列中去。clear函数顾名思义就是用来清除手势的,它在touchUp继onGestureEnded之后被调用,并且手势操作被取消时,onGesturePerformed依然会被回调,见上面touchUp的代码段。

因此,最终得到的接口函数调用顺序如下图所示:

Android开源项目-Jamendo音乐播放器研究与优化-手势操作_第1张图片

 

二、基于命令模式(Command Pattern)的手势控制

 

先说下命令模式,它是将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。命令模式的典型结构图如下所示:

Android开源项目-Jamendo音乐播放器研究与优化-手势操作_第2张图片

主要涉及四种角色:

1)接收者角色Receiver:真正执行命令的对象,任何一个类都可以成为接收者,只要他能完成命令要求实现的相应功能。

2)命令角色Command:实际包括一个Command接口和一个或者多个实现Command接口的具体命令ConcreteCommand类。实际命令对象通常持有Receiver的一个引用,并调用Receiver提供的功能来完成命令要执行的操作。

3)调用者角色Invoker:通常持有命令对象,并要求命令对象执行相应的请求,Invoker相当于是使用命令对象的入口。

4)装配者角色Client,用于创建具体的命令对象,并设置命令对象的接收者。

下面就参照命令模式的结构图,来看看Jamendo的手势操作实现代码,大部分定义在gesture包中,如图

Android开源项目-Jamendo音乐播放器研究与优化-手势操作_第3张图片

从类的名字可以大概看出各个类的责任分派:

1)抽象命令角色Command:GestureCommand接口,定义简单,整个接口只有一个execute函数:

public interface GestureCommand {
	void execute();
}

 

2)具体命令角色ConcreteCommand:PlayerGestureNextCommand、PlayerGesturePlayCommand、PlayerGesturePrevCommand、PlayerGestureStopCommand类,在这四个具体实现类中,都持有接收者角色PlayerEngine的实例,由它来完成具体的控制操作,以播放下一首这个命令为例,看下代码:

public class PlayerGestureNextCommand implements GestureCommand {

	PlayerEngine mPlayerEngine; //持有Receiver角色的一个引用
	
	// 将Receiver作为构造函数的参数传入,当然也可以使用Setter的方式传入,更灵活一些
	public PlayerGestureNextCommand( PlayerEngine engine ){
		mPlayerEngine = engine;
	}
	
	@Override
	public void execute() {
		Log.v(JamendoApplication.TAG, "PlayerGestureNextCommand");
		mPlayerEngine.next(); // 由Receiver完成具体的命令操作
	}
	
}

 

3)命令接收者角色Receiver:PlayerEngine本身是一个接口,定义了音频播放操作的一些通用函数,它有两个实现类,分别是IntentPlayerEngine和PlayerEngineImpl,这一部分放在后面文章分析,因为不属于本文的手势主题。

 

4)调用者角色Invoker:GestureHandler类,这个类是实现手势控制的关键类,它实现了OnGesturePerformedListener监听器,并重写了onGesturePerformed接口函数,在这个函数中首先判断手势库是否已经加载,在确保手势库正确加载的情况下,调用GestureLibrary.recognize进行手势识别,并在匹配度大于2.0时才确认识别到了预先定义的手势;手势识别到之后,就调用命令接口定义的execute函数,进行音乐播放的控制,代码如下:

public class GesturesHandler implements OnGesturePerformedListener {

	private GestureLibrary mLibrary; // 手势库
	
	private boolean mLoaded = false;

	private GestureCommandRegister mRegister; // 保存了已注册的控制命令(包括next、prev、play、stop)

	public GesturesHandler(Context context, GestureCommandRegister register) {
		// 从res/raw目录下加载gestures手势库
		mLibrary = GestureLibraries.fromRawResource(context, R.raw.gestures);
		load();
		setRegister(register);
	}

	/**
	 * 加载手势库
	 * @return
	 */
	private boolean load() {
		mLoaded = mLibrary.load();
		return mLoaded;
	}

	@Override
	public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
		if (!mLoaded) {
			if (!load()) {
				return;
			}
		}

		// 识别用户输入的手势
		ArrayList predictions = mLibrary.recognize(gesture);
		if (predictions.size() > 0) {
			Prediction prediction = predictions.get(0);
			Log.v(JamendoApplication.TAG, "Gesture " + prediction.name
					+ " recognized with score " + prediction.score);
			if (prediction.score > 2.0) { // 当手势匹配度大于2.0时,确定识别到了事先定义好的手势
				// 调用先前注册的命令接口定义的操作
				GestureCommand command = getRegister().getCommand(
						prediction.name);
				if (command != null) {
					command.execute();
				}
			}
		}
	}

	public void setRegister(GestureCommandRegister mRegister) {
		this.mRegister = mRegister;
	}

	public GestureCommandRegister getRegister() {
		return mRegister;
	}

}

 

5)装配者角色Client:由GestureCommandRegister类和PlayGestureCommandRegister类共同充当Client角色,GestureCommandRegister类维护了一个HashMap,用于充当各种具体命令角色的容器;PlayGestureCommandRegister是GestureCommandRegister的子类,主要实现注册next、prev、play和stop这四种具体播发控制命令角色的功能。

public class GestureCommandRegister {

	private HashMap mGestures;

	public GestureCommandRegister() {
		mGestures = new HashMap();
	}

	public void registerCommand(String name, GestureCommand gestureCommand) {
		mGestures.put(name, gestureCommand);
	}

	public GestureCommand getCommand(String name) {
		return mGestures.get(name);
	}

}

public class PlayerGestureCommandRegiser extends GestureCommandRegister {

	public PlayerGestureCommandRegiser(PlayerEngine playerEngine) {
		super();

		registerCommand("next", new PlayerGestureNextCommand(playerEngine));
		registerCommand("prev", new PlayerGesturePrevCommand(playerEngine));
		registerCommand("play", new PlayerGesturePlayCommand(playerEngine));
		registerCommand("stop", new PlayerGestureStopCommand(playerEngine));
	}

}

至此,Jamendo手势部分代码讲解完毕。

 

 

 

 

 


 

你可能感兴趣的:(Android开源项目-Jamendo音乐播放器研究与优化-手势操作)