更多创客作品,请关注笔者网站园丁鸟,搜集全球极具创意,且有价值的创客作品
ROS机器人知识请关注,diegorobot
业余时间完成的一款在线统计过程分析工具SPC,及SPC知识分享网站qdo
1.App有两个Activity
RobotChooser, 作为APP运行起来的Activity,配置为Main Action, 为用户呈现一个新增机器人,配置配置机器人的界面
ControlApp,在连接到Robot后的主界面,执行控制任务,及Ros Topic数据的接收,并显示在界面上,有兴趣的同学可以到https://www.diegorobot.com下载试用。
我们需要在项目的 文件中配置两个Activity,并吧RobotChooser配置为Main,如下图所示:
2. RobotChooser功能说明
RobotChooser为用户进入APP的第一个页面,主要提供如下功能
新增Robot,用户可以通过右上角的加号按钮来添加Robot,一个Robot代表一个Ros Master
Robot 列表,用户可以添加多个Robot Master,并列表显示
Robot 编辑,用户点击修改按钮,即可进入Robot信息的编辑页面,可以修改Master ip,及topic的名称
Robot 删除,用户也可以点击删除按钮删除Robot
Robot在线状态,Robot 列表中的信号图标如果是灰色,则表明Robot不在线,否则说明Robot是在线状态,可以连接
连接到Robot,当Robot处于在线状态的情况下,可以点击Robot项,直接连接到Robot,后跳转到控制页面
-
RobotChooser 代码主要逻辑说明
RobotChooser继承自AppCompatActivity,并实现了相应的接口,代码如下:
public class RobotChooser extends AppCompatActivity implements AddEditRobotDialogFragment.DialogListener,
ConfirmDeleteDialogFragment.DialogListener, ListView.OnItemClickListener {
/** Key for whether this is the first time the app has been launched */
public static final String FIRST_TIME_LAUNCH_KEY = "FIRST_TIME_LAUNCH";
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private ShowcaseView showcaseView;
private Toolbar mToolbar;
private ActionBarDrawerToggle mDrawerToggle;
// Variables for keeping track of Fragments
private Fragment fragment = null;
private FragmentManager fragmentManager;
private int fragmentsCreatedCounter = 0;
// Log tag String
private static final String TAG = "RobotChooser";
在RobotChooser变量声明部分,两个主要的变量是:
mRecyclerView, 这个是主要操作的界面视图类
mAdapter,这个类是Robot工具item,删除,编辑,连接到Robot的主要功能实现,对应的文件是Core/RobotInfoAdapter
showcaseView, 使用ShowcaseView实现在Robot列表中还没有添加Robot的情况下,实现操作引导界面
Android APP Activity的初始化代码一般放在onCreate(), RobotChooser的代码如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
this.setContentView(R.layout.robot_chooser);
}
catch(Exception e){}
mRecyclerView = (RecyclerView) findViewById(R.id.robot_recycler_view);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setLayoutManager(mLayoutManager);
mToolbar = (Toolbar) findViewById(R.id.robot_chooser_toolbar);
setSupportActionBar(mToolbar);
RobotStorage.load(this);
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
}
// Adapter for creating the list of Robot options
mAdapter = new RobotInfoAdapter(this, RobotStorage.getRobots());
mRecyclerView.setAdapter(mAdapter);
ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
// Check whether this is the first time the app has been launched on this device
final boolean isFirstLaunch = PreferenceManager
.getDefaultSharedPreferences(this)
.getBoolean(FIRST_TIME_LAUNCH_KEY, true);
// Delay the initial tutorial a little bit
// This makes sure the view gets a good reference to the UI layout positions
Runnable task = new Runnable() {
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (RobotStorage.getRobots().size() == 0 && isFirstLaunch) {
//Show initial tutorial message
showcaseView = new ShowcaseView.Builder(RobotChooser.this)
.setTarget(new ToolbarActionItemTarget(mToolbar, R.id.action_add_robot))
.setStyle(R.style.CustomShowcaseTheme2)
.hideOnTouchOutside()
.blockAllTouches()
//.singleShot(0) Can use this instead of manually saving in preferences
.setContentTitle(R.string.splash_add_robot_title)
.setContentText(R.string.splash_add_robot_text)
.build();
//Get ready to show tutorial message when user adds a robot
setupNextTutorialMessage();
}
} catch (Exception ignore) {}
}
});
}
};
worker.schedule(task, 1, TimeUnit.SECONDS);
}
在这段代码中首先指定了Activity对应的res id,初始化了mRecyclerView 等变量,这里需要关注的是RobotStorage.load(this)载入了已经配置好的Robot信息,返回一个Robot列表,并显示在主界面上。
isFirstLaunch 变量定义了用户是不是第一次打开APP,同时通过runOnUiThread运行一个独立的线程来执行ShowCaseView,实现功能引导,显示功能引导界面的条件是用户第一次使用,或者Robot list中没有Robot。
RobotChooser其他部分代码主要是正对Robot的增删改操作,及一些消息的传递,这里不在讲解。
-
RobotInfoAdapter 代码主要逻辑讲解
在Robot chooser界面针对当Robot的操作都是在RobotInfoAdapter中实现的,此文件位于Core文件夹下。其中内嵌了类ViewHolder来指定res,同时实现操作,其代码如下:
public ViewHolder(View v) {
super(v);
v.setClickable(true);
v.setOnClickListener(this);
mRobotNameTextView = (TextView) v.findViewById(R.id.robot_name_text_view);
mMasterUriTextView = (TextView) v.findViewById(R.id.master_uri_text_view);
mEditButton = (ImageButton) v.findViewById(R.id.robot_edit_button);
mEditButton.setOnClickListener(this);
mDeleteButton = (ImageButton) v.findViewById(R.id.robot_delete_button);
mDeleteButton.setOnClickListener(this);
mImageView = (ImageView) v.findViewById(R.id.robot_wifi_image);
mImageView.setImageResource(R.mipmap.wifi_0);
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
int position = getAdapterPosition();
final RobotInfo info = mDataset.get(position);
//mImageView.setLayoutParams(new ActionBar.LayoutParams(mEditButton.getHeight(), mEditButton.getHeight()));
if (isPortOpen(info.getUri().getHost(), info.getUri().getPort(), 10000)) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
mImageView.setImageResource(R.mipmap.wifi_4);
}
});
} else {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
mImageView.setImageResource(R.mipmap.wifi_0);
}
});
}
Thread.sleep(10000);
} catch (Exception ignore) {
}
}
}, 1000, 15000);
}
/**
* Handles clicks on the RobotInfoAdapter.ViewHolder.
*
* @param v The clicked View. Can be either the edit button, delete button, or the adapter itself,
* in which case a connection is initiated to the RobotInfo contained in this Adapter
*/
@Override
public void onClick(View v) {
int position = getAdapterPosition();
Bundle bundle;
final RobotInfo info = mDataset.get(position);
switch (v.getId()) {
case R.id.robot_edit_button:
AddEditRobotDialogFragment editRobotDialogFragment = new AddEditRobotDialogFragment();
bundle = new Bundle();
info.save(bundle);
bundle.putInt(AddEditRobotDialogFragment.POSITION_KEY, position);
editRobotDialogFragment.setArguments(bundle);
editRobotDialogFragment.show(activity.getSupportFragmentManager(), "editrobotialog");
break;
case R.id.robot_delete_button:
ConfirmDeleteDialogFragment confirmDeleteDialogFragment = new ConfirmDeleteDialogFragment();
bundle = new Bundle();
bundle.putInt(ConfirmDeleteDialogFragment.POSITION_KEY, position);
bundle.putString(ConfirmDeleteDialogFragment.NAME_KEY, info.getName());
confirmDeleteDialogFragment.setArguments(bundle);
confirmDeleteDialogFragment.show(activity.getSupportFragmentManager(), "deleterobotdialog");
break;
default:
FragmentManager fragmentManager = activity.getFragmentManager();
ConnectionProgressDialogFragment f = new ConnectionProgressDialogFragment(info);
f.show(fragmentManager, "ConnectionProgressDialog");
break;
}
}
}
在此类中,除了在开头部分指定了资源,主要有两部功能:
启动一个定时器任务,每10000毫秒检查一次Robot的在线状态,并同时更新界面的在线状态图标
处理onClick事件,根据用户点击的按钮执行相应的操作,当用户点击的是整个Robot的item时,则通过RobotinforAdapter的run函数跳转到ControlApp Action,界面切换为Robot的控制界面。
private void run()
{
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
if(!isPortOpen(INFO.getUri().getHost(), INFO.getUri().getPort(), 10000)){
throw new Exception(getActivity().getString(R.string.cannot_connect_ros));
}
final Intent intent = new Intent(activity, ControlApp.class);
// !!!---- EVIL USE OF STATIC VARIABLE ----!! //
// Should not be doing this but there is no other way that I can see -Michael
ControlApp.ROBOT_INFO = INFO;
dismiss();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.startActivity(intent);
}
});
}
catch (final NetworkOnMainThreadException e){
dismiss();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, "Invalid Master URI", Toast.LENGTH_LONG).show();
}
});
}
catch (InterruptedException e)
{
// Ignore
Log.d(TAG, "interrupted");
}
catch (final Exception e) {
if (ConnectionProgressDialogFragment.this.getFragmentManager() != null)
dismiss();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, e.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
}
});
thread.start();
}