1.App有兩個Activity
RobotChooser, 作爲APP運行起來的Activity,配置爲Main Action, 爲用戶呈現一個新增機器人,配置配置機器人的界面
ControlApp,在連接到Robot後的主界面,執行控制任務,及Ros Topic數據的接收,並顯示在界面上
我們需要在項目的 文件中配置兩個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();
}