步骤1:设置
此“设置”部分主要用于Android Studio;
与其他任何新应用一样,为它提供一个适当的名称。我称自己为“ JoystickTest”,因为那正是它的本质。复制完该类后,您仍然可以将操纵杆导出到其他项目中。
对于该版本,我认为2.2以后的版本都可以正常工作。不过我使用了4.2,因为它与我向其中添加操纵杆的应用程序的版本相同。
最后,它会要求您输入一个Activity。我使用了一个空的Activity,因为没有当根本不需要布局XML时,就不需要花哨的额外组件。 (我们将在稍后进行讨论)。将其命名为MainActivity以便于参考。
生成项目后,您应该有一个名为MainActivity的Java类。
步骤2:SurfaceView构造函数/更多设置
SurfaceView小部件是构建游戏杆的基础。它提供了对绘图表面和方法的访问,我们可以使用这些方法和方法来检测触摸的屏幕。
首先创建一个新对象。将其命名以反映用途-我将其命名为JoystickView。创建后,让该类扩展SurfaceView。这样,该类可以访问SurfaceView的所有字段和方法,同时还可以像SurfaceView一样将其添加到UI中。添加“扩展”之后,您的开发环境应为您提供有关具有错误构造函数的代码错误。您应该重写SurfaceView类的所有三个构造函数。通常,您不需要向其中添加任何其他内容。现在,只需使用构造函数内部的参数调用“ super”即可;我们将在以后实现功能时添加更多内容。
您需要重写的构造函数为:
public SurfaceView(上下文c)
public SurfaceView (上下文c,AttributeSet a,int样式)
公共SurfaceView(上下文c,AttributeSet a)
请注意,不必全部覆盖这三个变量-您可以不必担心仅覆盖第一个。通过覆盖所有这三个,您将能够直接将操纵杆添加到XML布局中,从而使其更易于实现到UI中。
SurfaceView回调
在SurfaceView生命周期的适当时刻,还需要一些回调来初始化内容。回调是类中的对象可以通知类其中发生了特定事件的一种方式。这可能是因为对象已完成加载(例如,如果它是UI元素),或者对象已完成特定功能。在Android中,这通常是通过Interfaces完成的。该类仅实现对象所具有的接口,在接口中添加方法,然后在发生某些事件时对象调用所述方法。我们需要实现SurfaceView.CallBack接口。将“ implementation SurfaceView.Callback”添加到Java文件的类声明中,紧随“ extends SurfaceView”之后。这将添加以下三个回调方法:
public void surfaceCreated(SurfaceHolderholder)
public void surfaceChanged(SurfaceHolderholder,int格式,int宽度,int高度)
public void surfaceDestroyed(SurfaceHolderholder)如果使用的是Android Studio,它将提示您立即添加方法。在我们之前设置的三个构造器中,每个构造器还添加:getHolder()。addCallback(this);
在这三个回调方法中,我们只真正关心surfaceCreated,因为在SurfaceView发生这些事件时将调用该方法,从而将此类中的回调方法设置为在这些事件发生时要调用的方法。已经完全创建并具有所有尺寸并可以进行绘制。
MainActivity
我们将不得不按顺序更改MainActivity.java文件。轻松调试JoystickView。使用SurfaceView(Context c)构造函数(将上下文设置为“ this”),在MainActivity的onCreate方法中创建JoystickView的新实例。然后,将“ setContentView”方法括号中的内容替换为新游戏杆实例的名称。
步骤3:绘制游戏杆
,这只会使其默认情况下仅显示JoystickView。
首先,简要说明一下如何在Android的SurfaceView上进行绘制:
-在Android的SurfaceViews上进行绘制通过Canvas对象完成操作,该对象保存了所有要显示给用户的形状/图像。
-在画布上绘制就像在笛卡尔平面上绘制一样。它具有X轴和Y轴,分别向左和向左增加,并以像素为单位。形状放置在此平面上的坐标点上。
-在画布上绘制是添加的,这意味着当两个形状重叠时,最新绘制的形状将隐藏使用较早命令绘制的形状。这意味着您必须始终先绘制基础。
-画布上的像素是相对于设备屏幕尺寸的像素。例如,在1280 * 720屏幕上水平占据设备宽度而在垂直方向上占据设备高度1/2的画布,其像素的宽度和高度将不会与1920 * 1080屏幕相同。第一个将具有WxH 1280 * 360,而第二个将具有1920 * 540,即使它们在两个设备上占用的屏幕数量相同。
现在进入实际编程。
全局变量和尺寸设置
由于画布的长度和宽度是根据它们在屏幕上占据的像素数来定义的,因此这样做是不明智的对操纵杆的坐标或其半径进行硬编码,因为即使SurfaceView的尺寸发生变化,操纵杆的坐标或半径也会在画布上保持相同的位置和尺寸。为了使其自动适应画布的大小,我们必须使用全局变量并在运行时计算操纵杆的位置和大小。
我们将需要四个全局变量,如下所示:
float centerX
float centerY
float baseRadius
float hatRadius
我们还需要一个方法,void setupDimensions(),给变量赋值在其中放入以下内容:
centerX = getWidth()/2;
centerY = getHeight()/2;
baseRadius = Math.min( getWidth(),getHeight())/3;
hatRadius = Math.min(getWidth(),getHeight())/5;
此代码仅将值分配给每个使用“ getWidth()”和“ getHeight()”方法访问的变量,以SurfaceView的宽度和高度之比表示。 Math.min确保操纵杆将始终适合SurfaceView内,即使一个尺寸比另一个尺寸小得多。您可以继续为每个比例分配自己的比例,此处建议仅用于完全居中的操纵杆。
完成后,将setupDimensions()方法添加到surfaceCreated方法中以获得操纵杆的正确位置。我们在此方法中调用setupDimensions(),因为到此为止,我们知道SurfaceView已被初始化,因此尝试获取其属性不会导致任何异常。
绘图方法
绘制操纵杆的方法实际上非常简单。创建一个接受两个浮点参数的新方法-newX和newY:
private void drawJoystick(float newX,float newY)
newX和newY将用于指定帽子的位置(操纵杆顶部)。
现在,请记住,SurfaceView上的所有绘制都是通过其Canvas对象完成的。您需要访问此Canvas才能绘制操纵杆。您可以通过调用以下方法来做到这一点:
Canvas myCanvas = this.getHolder()。lockCanvas();
这将返回一个Canvas对象,您应该将其分配给变量以方便参考
您还将需要一个Paint对象,该对象代表您当前使用的颜色。只需创建并实例化它即可。
绘制颜色= new Paint();
现在您可以绘制了。首先,清除画布上的所有内容:
myCanvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR);
接下来,我们绘制基础。这是一个始终停留在同一位置的简单圆圈。首先,通过调用以下命令设置颜色:
colors.setARGB(int alpha,int red,int green,int blue);
每个变量必须是介于0到255之间的int 。Alpha决定透明度,其中255为纯色,0为不可见色。要获得更直观的版本,只需谷歌“ RGB颜色选择器”。对于我的游戏杆,我使用了浅灰色,具有aRGB值(255、50、50、50),但是可以随时使用自己的颜色。
现在您可以绘制底座了。调用Canvas的drawCircle方法。此方法按此顺序需要4个参数:(中心的x位置为float,y的中心位置为float,圆的半径为float,圆的颜色为绘制对象)。在上一节中,我们已经定义了其中三个:
myCanvas.drawCircle(centerX,centerY,baseRadius,color)
接下来,绘制帽子。再次调用Paint对象的setARGB方法为帽子设置其他颜色。我使用了纯蓝色,并带有aRGB组合(255、0、0、255)。然后调用drawCircle方法,仅这次使用newX和newY作为绘制帽子的坐标,并使用全局变量作为帽子的半径。
myCanvas.drawCircle(newX,newY,radiusHat,color )
操纵杆现已绘制,但尚未打印到SurfaceView上,并且用户不可见。为此,请调用:
getHolder()。unlockCanvasAndPost(myCanvas);
其中myCanvas是Canvas变量的名称。
最后,您应该用以下“ if”语句包围整个方法:
if(getHolder()。getSurface()。isValid())
此if语句阻止绘图方法在以下情况下执行
完成此方法后,将其附加到上一节中我们覆盖的surfaceCreated方法中,其中newX = centerX和newY = centerY 。应用启动后,这将绘制一个操纵杆,其顶部位于中间位置。没有这个,在调用该方法之前,您只会看到一个黑框。
恭喜!您已经绘制了一个操纵杆。但是,直到我们将面板配置为接受用户输入后,它才能真正执行任何操作。
步骤4:添加交互性
要允许用户交互和移动操纵杆,我们将必须实现OnTouchListener接口。这个界面迫使我们添加onTouch方法,当用户触摸屏幕时(或者实际上以任何方式与其交互),该方法会自动调用。使用它,您可以响应触摸事件,但是我们需要这样做。在我们的例子中,它将把操纵杆移动到适当的位置。
首先,通过在类声明的“ implements”之后添加“ View.OnTouchListener”来实现OnTouchListener。在Java中,您可以根据需要实现任意数量的接口,但是必须用逗号分隔。还添加以下实现方法:
公共布尔onTouch(View v,MotionEvent e)
这将覆盖OnTouchListener界面中的onTouch方法。每当用户触摸屏幕时,都会调用此方法,从而通过其视图向我们提供用户触摸的视图及其触摸方式(例如,轻击屏幕,在屏幕上移动手指,放开屏幕等)两个论点。在该方法中,我们必须添加我们自己的代码以响应用户触摸它。
在该方法中,首先添加以下“ if”语句
if(v .equals(this))
这可确保触摸侦听器仅接受来自此SurfaceView的触摸。在该if语句中,添加另一个:
if(e.getAction!= e.ACTION_UP)
这将检查触摸事件是否不是用户将手指从触摸上移开屏幕。我们需要这样做,以确保操纵杆仅在用户触摸屏幕时才移动,并在用户放开时重置到其原始位置(就像真正的操纵杆一样)。在此“ if”条件下,调用您在上一步中制作的操纵杆绘制方法:
drawJoystick(e.getX(),e.getY());
The getX()和getY()方法分别以用户触摸屏幕的像素为单位提供X和Y坐标。将它们发送到drawJoystick方法会使操纵杆的帽子在这些位置绘制。
接下来,在if之后添加“ else”语句。仅在与if语句相反的情况为true时(即,如果“ e.getAction == e.ACTION_UP”,即释放操纵杆),则执行else语句。在其中放置:
drawJoystick(centerX,centerY);
这将在用户放开时将操纵杆重置为其中心位置。
最后,添加“返回真实;”到方法的末尾,在if语句之外。我们返回true,因为返回false会阻止onTouch方法接收将来的触摸。
目前,此方法与此有关。返回到每个构造函数,并向其添加以下行:
setOnTouchListener(this);
添加此行将使SurfaceView使用此类中的onTouch方法
此时,从现在开始处理用户的屏幕触摸。
在运行应用程序时,您应该可以点击屏幕并在其中移动操纵杆。
第5步:约束操纵杆
如果您测试了该应用,可能已经注意到,操纵杆的顶部可以飞离操纵杆的底部,这显然不是真正的操纵杆应该表现的!游戏杆帽子永远不要离开底座,否则看起来不切实际,并可能与应用程序中的其他视图重叠。
检查边界。..在数学上
要解决此问题,我们必须在onTouch方法中添加一些内容,以检查用户是否在操纵杆底部的边界之外单击,如果是,请绘制操纵杆帽以使其位于底部的边界,但仍沿用户敲击的方向(因此,在操纵杆底座外部略微敲击不会使该东西不对齐)。如果这样做没有道理,请查看其中的一些图片。
检查用户是否在范围之内是其中的简单部分。我们可以简单地计算出操纵杆从静止位置的总位移,并将其与操纵杆的半径进行比较。如果位移大于半径,则用户必须将操纵杆移至底座范围之外。这是因为基部是一个圆,并且基部边缘在沿圆的任何点处相对于圆心的位移始终是恒定的。再次查看图表以了解更多信息。
要计算此位移,我们必须使用勾股定理:
a ^ 2 + b ^ 2 = c ^在图2中,ab和c组成一个三角形。
假设用户单击的点具有坐标(x‘,y’)和中心(x,y)。我们还假设a是x位置的变化,b是y位置的变化。因此,c将是离中心的净位移。因此,c可以表示为:
sqrt((x‘-x)^ 2 +(y’-y)^ 2)
或用Java术语表示,具体针对我们的程序:
浮点位移=(float)Math.sqrt(Math.pow(e.getX()-centerX,2)+ Math.pow(e.getY()-centerY,2));
其中e是我们的onTouch方法中的MotionEvent。在(e.getAction()!= e.ACTION_UP)if语句的顶部添加该段代码。之所以将其放置在此处,是因为仅在用户实际移动棒时(即仅在用户触摸SurfaceView时),才需要测试用户是否在范围内移动棒。
添加另一个在drawJoystick之前的if语句,以检查净位移是否小于基本半径。如果是这样,则单击有效,并且我们不必限制操纵杆帽子。
if(displacement
还添加else语句。这可以处理位移大于基本半径的情况,这意味着我们必须将操纵杆限制为基本半径。
约束操纵杆
要进行约束,我们将使用平行三角形标识。这基本上就是说,如果您有两个三角形,两个三角形具有两个平行的边且具有相同的角度,则所有三个边的比率将相同。可以在这里找到更好的解释:
https://www.mathsisfun.com/geometry/triangles-simi 。..
因为我们要保持约束的角度操纵杆与越界操纵杆的操纵杆相同,我们知道角度必须相同。我们也知道,两个形成的三角形都必须是直角三角形,因为臂将始终由(x‘-x)和(y’-y)形成,也就是沿x和y轴相对于x的位移中心点。因此,至少两侧将是平行的,并且身份将生效。现在,我们可以通过将斜边的已知位移除以找到其他两个位移的长度。
斜边的比率可以通过以下公式求出:
浮点比率= baseRadius/位移;
我们将位移除以距离,因为我们希望另一个成为新的净位移,因为它会将操纵杆限制在底座上。现在,将x和y位移的值相乘以获得新的位移,以便将操纵杆约束到底部。
float constrainedX = centerX +(e.getX()-centerX)* ratio;
;请确保将centerX和centerY添加到适当的值,因为它们是相对于中心的新位移。 》
float constrainedY = centerY +(e.getY()-centerY)* ratio;
然后对受约束的值调用drawJoystick方法以绘制新的操纵杆。如果您正确完成了所有操作,则在操纵杆基座的边界之外单击仍会导致操纵杆移动,但始终会保持在基座内。
drawJoystick(constrainedX,constrainedY);
如果其中任何一个令人困惑,希望附图能更好地在视觉上显示它。添加drawJoystick之后,测试您的应用程序。无论您在何处触摸屏幕,操纵杆现在都应停留在底座内。
步骤6:与其他活动进行交互
好的,很好,现在您有了一个基本但功能齐全的操纵杆,可以随意移动!但是,您还不能做任何事。如果需要,您将无法与其他活动进行交互。那就是添加回调方法的地方。
还记得几步之前的回调方法吗?我们现在需要一个,以便操纵杆可以报告它被触摸了,以及它在您要使用它的任何其他物体中被触摸的方式。要创建此回调方法,您首先必须在JoystickView中声明一个Interface:/p》
公共接口JoystickListener
{
void onJoystickMoved(float xPercent,float yPercent,int source);
}
您真的可以在参数中添加任何内容。您可以放置其被点击的位置,总的点击位移等。在我计划使用它来控制电动机功率时,我输入它从其可能的总位移中移出的百分比。如果您打算使用多个操纵杆,则需要在最后加上“ source” int,因为这样可以区分它们。
接下来,创建一个名为“ joystickCallback”的JoystickListener全局实例。 。在每个构造函数中,添加:
if(JoystickListener的上下文实例)
joystickCallback =(JoystickListener)上下文;
其中joystickCallback是全局JoystickListener和上下文是构造函数中的上下文参数。
这使我们可以在表示包含此操纵杆的活动的类中调用onJoystickMoved方法,只要该实现已实现JoystickListener并具有适当的onJoystickMoved方法即可。在任何时候,您都可以调用
joystickCallback.onJoystickMoved(xPercent,yPercent,getId());
,例如,它将在实现过程中调用onJoystickMoved方法听众。将其放置在onTouch方法中将是最有效的,因为这样它就可以中继有关操纵杆如何移动到其父方法的信息。您必须对该方法进行三个不同的调用。请注意,在本节中,我将使用返回百分比的实现。
在if(displacement
joystickCallback.onJoystickMoved(( e.getX()-centerX)/baseRadius,(e.getY()-centerY)/baseRadius,getId());
由于杆在每个轴上的拉动距离会有所不同。在下面的else语句中,我将放置:
joystickCallback.onJoystickMoved((constrainedX-centerX)/baseRadius,(constrainedY-centerY)/baseRadius,getId());
此的原因与上述相同。在此之后的else语句中,我将放置:
joystickCallback.onJoystickMoved(0,0,getId());
,因为else条件仅在用户放开时执行屏幕上的按钮,操纵杆移回中间位置。
现在可以在“活动”中使用操纵杆了。要对其进行测试,请返回您的MainActivity类。添加“实现JoystickView.JoystickListener”并向其添加回调方法。在回调方法中,添加
Log.d(“ Main Method”,“ X percent:” + xPercent +“ Y percent:” + yPercent);
xPercent,yPercent是从JoystickView传递的参数。现在,当您在测试应用中移动操纵杆时,您应该在Android调试器中看到操纵杆在每个轴上被推入多远的百分比。 Y百分比向下增加,而X百分比向右增加。负百分比仅表示您将其推向另一个方向(例如,左移而不是右移)。如果一切正常,那么恭喜!您已经成功创建了Android游戏杆。
第7步:通过XML添加JoystickView
拥有此功能的真正好处之一操纵杆扩展了SurfaceView,是您可以直接通过XML将其添加。您还可以像其他任何视图一样设置有关它的信息,设置名称,ID等。我们可以通过进入activity_main.xml并编辑布局来进行测试。
如果您使用的是在Android Studio中,默认情况下activity_main.xml应该只是一个空白的RelativeLayout。为了简单起见,我将其更改为带有android:orientation =“ vertical”的LinearLayout。我们可以通过在LinearLayout中添加:
,通过XML将JoystickView添加到布局中,其中your_package_name是您在创建项目时指定的包的名称。与其他视图一样,要使其与Android一起使用,您必须添加layout_width和layout_height字段。
android:layout_width =“ match_parent”
android:layout_height =“ 300dp“
/》
我只是使用300dp,因为它看起来很合理。当然,您可以使用布局权重,因为这是LinearLayout,但是为了简单起见,我对大小进行了硬编码。在XML预览中,您现在应该看到一个标有“ JoystickView”的大灰色框(也就是说,如果您使用的是Android Studio)。现在,您可以返回MainActivity.java文件,并将setContentView中的参数替换为XML布局(在Android Studio中为R.layout.activity_main)。现在运行您的应用程序将显示操纵杆,仅按比例缩小以适合您在布局中指定的尺寸。
至此,您已经基本完成。恭喜!您已经成功创建了Android游戏杆。要在其他应用程序中使用此游戏杆,只需复制JoystickView java文件并更改程序包名称,使其适合您的新应用程序的其余部分。要在其他Activity布局中使用操纵杆,只需在新的Activity XML中重复本节中的操作即可。
此后的其余步骤仅是不必要的。我们将讨论如何使用操纵杆进行着色,以及如何在一个活动中实现多个操纵杆。
步骤8:附加功能:多个操纵杆,1个活动
在活动中只有一个操纵杆时,接收输入很简单。当您有多个操纵杆时,它会变得更加复杂。由于我们实现了操纵杆回调的方式,您活动中的所有操纵杆都将通过调用相同的onJoystickMoved方法与活动进行通信。为了正确处理它们,您必须区分它们。
不要害怕!这实际上是非常简单的。在XML中添加操纵杆时,只需为每个操纵杆的id标记赋予一个值。在此示例中,我添加了两个操纵杆。我将第一个操纵杆的ID设置为:
android:id =“ @ + id/joystickRight”
,第二个操纵杆的ID设置为:
android:id =“ @ + id/joystickLeft“
然后,在onJoystickMoved方法中,添加一个switch语句来区分操纵杆并对其进行处理:
switch(id)
{
case R.id.joystickRight:
Log.d(“ Right Joystick”,“ X:” + xPercent +“ Y:” + yPercent);
break;
case R.id.joystickLeft:
Log.d(“ Left Joystick”,“ X:” + xPercent +“ Y:” + yPercent);
break;
}
Android为每个id分配了一个特定的int值,可以通过调用“ R.id.view_name_here”进行访问。在具有指定名称的视图中调用getId()也会返回特定于其自身的值。由于我们较早地对回调进行了编程,以将该特定值发送到它所附加的Activity,并且我们已经知道分配给您的操纵杆的所有ID,因此我们可以使用switch语句来解析它们。而已!现在运行该应用程序,然后尝试移动每个操纵杆。
第9步:其他功能:更漂亮,带阴影的操纵杆
尽管此时操纵杆可以正常工作,它看起来确实。..。沉闷。我们可以通过修改我们的绘制方法来快速纠正它,以便它做一些阴影处理。最终产品是一个操纵杆,它不仅具有不错的颜色渐变,而且具有适当的柄!
更改颜色-帽子
首先,我们必须用“ for”循环将baseA和hat包围着drawJoystick方法的setARGB和drawCircle部分。它们应采用以下形式:
for(int i = 1; i 《= baseRadius/RATIO; i ++)
和
for(int i = 1; i 《= hatRadius/RATIO; i ++)
RATIO是您应声明的常量。它允许我们稍后调整要添加到操纵杆的阴影量。请记住,阴影更多=手机负载更高!我们将循环的长度相对于基部/帽子的半径进行调整,因为较大的操纵杆将需要更多阴影才能看起来正确。
接下来,我们将逐渐调整这两种颜色的颜色,以创建一个某种阴影效果。我们将从操纵杆帽子开始,因为它比较简单。在这里,我们希望将颜色从操纵杆的深蓝色转换为较浅的白色,以模拟从顶部发出的光。为此,我们只需使其随i的增加而增加,因此红色和绿色值相同,而蓝色的值则保持在255不变。这使蓝色变得越来越浅。将循环中的setARGB方法替换为:
colors.setARGB(255,(int)(i *(255 * RATIO/hatRadius)),(int)(i *(255 * RATIO/hatRadius) ),255);
为什么将i乘以(RATIO/hatRadius)?这样可以确保在循环结束时,红色和绿色的值均为255,从而产生白色的整体颜色。当i = hatRadius/RATIO时,循环结束,因此i的最终值将是hatRadius/RATIO,该值会被(RATIO/hatRadius)抵消,结果只有255。
更改颜色-基本
这一次,我们使用了重叠的阴影技术。我们减小alpha的值,以使绘制的形状有些透明,然后将这些形状彼此叠加。重叠的形状将融合,并创建更接近阴影颜色的色相。将此循环中的setARGB方法替换为:
colors.setARGB(150/i,255,0,0);
您可以将后三个参数设置为所需的任何值,它们只是定义阴影色调的颜色。无论如何,我们正在使用此循环为操纵杆绘制茎,并且颜色无关紧要。我用了一根红色的茎。 150/i确保当循环接近完成时,alpha接近零,并且最靠近茎末端的位变得不可见
阴影-帽子
只需将drawCircle方法中的第三个参数替换为:
hatRadius-(float)i *(ratio)/3
所有这些操作是逐渐减小绘图半径阴影,直到它达到操纵杆帽子半径的1/3的最小值为止。 1/3半径是顶部的发亮的白色部分。
阴影-茎杆
操纵杆茎杆要复杂一些。我们希望将操纵杆柄的根部精确地保持在操纵杆基部的中心,同时用阴影产生一种透视效果以创建柄。
-
Android
+关注
关注
12文章
3923浏览量
127119 -
游戏操纵杆
+关注
关注
0文章
4浏览量
8303
发布评论请先 登录
相关推荐
评论