可扩展列表视图在android操作系统中非常缓慢
我的可扩展可扩展列表视图在android操作系统中非常缓慢,android,android-studio,scrollbar,expandablelistview,Android,Android Studio,Scrollbar,Expandablelistview,我的可扩展列表视图滚动速度非常慢,当我单击父类别时需要一段时间,直到我看到子视图 组列表活动: public class GroupsListActivity extends Activity { String loggedUserId = Model.getInstance().getLoggedUserId(); List<String> groupsList; static ExpandableListView expandableListView;
列表视图
滚动速度非常慢,当我单击父类别时需要一段时间,直到我看到子视图
组列表活动:
public class GroupsListActivity extends Activity {
String loggedUserId = Model.getInstance().getLoggedUserId();
List<String> groupsList;
static ExpandableListView expandableListView;
HashMap<String, List<Group>> groupCategories = new HashMap<String, List<Group>>();
static ProgressBar spinner;
static TextView textLoading;
ImageButton createCategoryButton;
static Adapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set layout for this activity
setContentView(R.layout.expandable_list);
// Set actionbar title
getActionBar().show();
getActionBar().setTitle(Html.fromHtml("<font color='#fffffff'>Groups</font>"));
if (loggedUserId != null)
Log.d("TAG", "My Groups for user ID: " + loggedUserId);
// Connect between buttons to layout id
expandableListView = (ExpandableListView) findViewById(R.id.exp_list);
spinner = (ProgressBar) findViewById(R.id.spinner);
createCategoryButton = (ImageButton) findViewById(R.id.createCategory);
textLoading = (TextView) findViewById(R.id.textLoading);
// Loading data to expandable group list asynchronously
AsyncTask<String, String, HashMap<String, List<Group>>> task = new AsyncTask<String, String, HashMap<String, List<Group>>>() {
@Override
protected HashMap<String, List<Group>> doInBackground(String... params) {
return DataProvider.getInfo();
}
@Override
protected void onPostExecute(HashMap<String, List<Group>> listHashMap) {
super.onPostExecute(listHashMap);
// Setting adapter and creating group list
groupCategories = listHashMap;
groupsList = new ArrayList<String>(groupCategories.keySet());
adapter = new Adapter(GroupsListActivity.this, groupCategories, groupsList, GroupsListActivity.this);
expandableListView.setAdapter(adapter);
// Hide spinner after loading
spinner.setVisibility(View.GONE);
textLoading.setVisibility(View.GONE);
}
};
task.execute();
// Setting listener for group click
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v, int parentPosition, int childPosition, long id) {
// After selecting a group on row - open contacts list for this group
expandableListView.setEnabled(false);
openContactListForGroup(groupCategories.get(groupsList.get(parentPosition)).get(childPosition).getGroupID());
return true;
}
});
// Setting listener for create group click
createCategoryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createCategoryButton.setEnabled(false);
onCategoryCreate(GroupsListActivity.this, createCategoryButton);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_actionbar_groups, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create:
onCreate();
return true;
case R.id.action_search:
onSearch();
return true;
case R.id.action_favorites:
onFavorites();
return true;
case R.id.action_settings:
onSettings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// Menu methods
private void onCreate() {
Log.d("TAG", "Create button was pressed");
Intent i = new
Intent(getApplicationContext(),
CreateGroupActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
private void onSearch() {
Log.d("TAG", "Search button was pressed");
Intent i = new
Intent(getApplicationContext(),
SearchActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
private void onFavorites() {
Log.d("TAG", "Favorites button was pressed");
Intent i = new
Intent(getApplicationContext(),
FavoritesListActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
private void onSettings() {
Log.d("TAG", "Settings button was pressed");
// Settings activity
Intent i = new
Intent(getApplicationContext(),
SettingsActivity.class);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
// Methods to handle action buttons
private void onCategoryCreate(final Activity activity, final ImageButton createCategoryButton) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
createCategoryButton.setEnabled(true);
final String title = "Create a new category";
String message = "Type a name for your new category";
// Set dialog edit_text
final EditText categoryNameTextView = new EditText(activity);
categoryNameTextView.setHint("Type your category name");
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
categoryNameTextView.setLayoutParams(lp);
builder.setView(categoryNameTextView);
// Set dialog title and message
if (title != null)
builder.setTitle(Html.fromHtml("<font color='#dc1c1c'>" + title + "</font>")).setMessage(message);
// Set dialog buttons
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final String newCategoryName = categoryNameTextView.getText().toString();
// Check if contains only spaces
if (!(newCategoryName.trim().length() > 0))
Toast.makeText(activity, "Type at least 1 letter to create the category", Toast.LENGTH_LONG).show();
// Check if category name already exists
else if (groupsList.contains(newCategoryName))
Toast.makeText(activity, newCategoryName + " already exist. Please type another category name", Toast.LENGTH_LONG).show();
else {
// Create a new category in server and add user to a sample group
adapter.getCategoriesList().add(newCategoryName);
adapter.getGroupsList().put(newCategoryName, Collections.<Group>emptyList());
// Update adapter and show toast to user
GroupsListActivity.updateAdapter();
Toast.makeText(activity, "You created " + newCategoryName + " category", Toast.LENGTH_LONG).show();
}
}
});
builder.setNegativeButton(
"Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.setIcon(R.mipmap.edit);
builder.show();
}
public void openContactListForGroup(String groupId) {
// Contacts List activity
Intent i = new
Intent(getApplicationContext(),
ContactsListActivity.class);
// Pass to details activity the logged group id and start activity
Bundle b = new Bundle();
b.putString("groupId", groupId);
i.putExtras(b);
startActivity(i);
overridePendingTransition(R.animator.slide_out_right, R.animator.slide_in_right);
}
// Static methods to use from other activities
public static void updateAdapter() {
spinner.setVisibility(View.VISIBLE);
adapter.notifyDataSetChanged();
// Hide spinner after adapter finish the update
expandableListView.post(new Runnable() {
@Override
public void run() {
spinner.setVisibility(View.GONE);
}
});
}
public static void addGroupToList(String groupId) {
Model.getInstance().getGroup(groupId, new Model.groupReturnedListener() {
@Override
public void addGroupToLocal(Group group) {
// Add group to category Others in Group List Activity
if (adapter.getGroupsList().get("Others").size() == 0) {
// Add group to empty list
List<Group> list = new LinkedList<Group>();
list.add(group);
adapter.getGroupsList().put("Others", list);
adapter.notifyDataSetChanged();
} else {
// Add group to an existing list
adapter.getGroupsList().get("Others").add(group);
adapter.notifyDataSetChanged();
}
}
});
}
public static void removeGroupFromList(String groupId) {
int position = -1;
// Get category position
String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(groupId);
List<Group> data = adapter.getGroupsList().get(oldCategoryName);
// Search for group position
for (Group group : data) {
if (group.getGroupID().equals(groupId)) {
position = data.indexOf(group);
break;
}
}
// Groups was found
if (position != -1) {
data.remove(position);
adapter.notifyDataSetChanged();
}
}
public static void updateGroupFromList(Group group) {
int position = -1;
// Get category position
String oldCategoryName = Model.getInstance().getCategoryNameByGroupId(group.getGroupID());
List<Group> data = adapter.getGroupsList().get(oldCategoryName);
// Search for group position
for (Group groupIterator : data) {
if (groupIterator.getGroupID().equals(group.getGroupID())) {
position = data.indexOf(groupIterator);
break;
}
}
// Groups was found
if (position != -1) {
data.remove(position);
data.add(group);
adapter.notifyDataSetChanged();
}
}
// Other methods
@Override
protected void onResume() {
super.onResume();
expandableListView.setEnabled(true);
}
@Override
public void onBackPressed() {
ExitDialog exitDialog = new ExitDialog(GroupsListActivity.this);
exitDialog.show();
}
}
加载图像的方法:
public void getGroupImage(final String imageName, final LoadImageListener listener) {
AsyncTask<String, String, Bitmap> task = new AsyncTask<String, String, Bitmap>() {
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bmp = loadImageFromFile(imageName); //first try to find the image on the device
// Bitmap bmp = null;
if (bmp == null) { //if image not found - try downloading it from parse
bmp = modelParse.getGroupImage(imageName);
if (bmp != null)
saveImageToFile(bmp, imageName); //save the image locally for next time *****
}
Bitmap scaledBitmap = scaleDown(bmp, 200, true);
return scaledBitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
listener.onResult(result);
}
};
task.execute();
}
private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
FileOutputStream fos;
OutputStream out = null;
try {
File dir = context.getExternalFilesDir(null);
out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private Bitmap loadImageFromFile(String fileName) {
Bitmap bitmap = null;
try {
File dir = context.getExternalFilesDir(null);
InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bitmap;
}
public void getGroupImage(最终字符串imageName,最终加载ImageListener侦听器){
AsyncTask任务=新建AsyncTask(){
@凌驾
受保护位图doInBackground(字符串…参数){
位图bmp=loadImageFromFile(imageName);//首先尝试在设备上查找图像
//位图bmp=null;
如果(bmp==null){//如果找不到图像-请尝试从解析下载它
bmp=modelParse.getGroupImage(imageName);
如果(bmp!=null)
saveImageToFile(bmp,imageName);//在本地保存图像以备下次使用*****
}
位图缩放位图=缩放向下(bmp,200,真);
返回缩放位图;
}
@凌驾
受保护的void onPostExecute(位图结果){
onResult(result);
}
};
task.execute();
}
私有void saveImageToFile(位图图像位图、字符串图像文件名){
文件输出流;
OutputStream out=null;
试一试{
File dir=context.getExternalFilesDir(null);
out=newfileoutputstream(新文件(dir,imageFileName+“.jpg”);
imageBitmap.compress(Bitmap.CompressFormat.PNG,100,out);
out.close();
}catch(filenotfounde异常){
e、 printStackTrace();
}捕获(IOE异常){
e、 printStackTrace();
}
}
专用位图loadImageFromFile(字符串文件名){
位图=空;
试一试{
File dir=context.getExternalFilesDir(null);
InputStream InputStream=新文件InputStream(新文件(dir,fileName+“.jpg”);
位图=BitmapFactory.decodeStream(inputStream);
}catch(filenotfounde异常){
e、 printStackTrace();
}
返回位图;
}
我发现性能方面存在两个主要问题
首先,到处使用findViewById。您正在遍历整个视图树。这就是人们使用ViewHolder模式或自定义视图模式的原因。如果在应用程序的生命周期内,每个视图多次使用findViewById,那么你就错了
第二,每次用户滚动时都分配新对象。不要。使用ViewHolder或vustom视图模式,以便可以对所有滚动事件重复使用相同的OnClickListener,每行仅创建一次,并根据需要更新值。如果在getView中创建对象(convertView为null时除外),则效率非常低。我将尝试给出有关ListView和适配器的一般(抽象)概念,这将帮助您自己找出错误的部分
适配器的全部用途是显示相应列表项的正确数据,并尽可能少地执行其他工作。在此过程中,任何与数据操作相关的操作都需要cpu周期,这将导致延迟和缓慢滚动
具体来说,android应用程序应该以每秒60帧的平滑速度运行,并且每个帧的渲染时间不应该超过16.6毫秒,以达到60FPS的速度。因此,如果您正在为CPU创建额外负载,则可能是由于帧渲染造成的,并且从这里到渲染滞后的路径很短
我想说的是——适配器中可能有一些方法在现场同步地处理数据,这会让cpu负担过重。适配器应该表示已经准备好显示的数据,并且只在正确的视图中显示它。性能问题的一个例子可能和每次对每个视图使用String.replace()方法一样简单,或者另一个糟糕的例子是同步加载图像而不是异步加载图像。我刚才对此很感兴趣,你能提供一个有效的教程吗?。如果我做对了,我应该在第一次加载时初始化按钮(findviewbyd),然后使用我在滚动之前得到的“旧”信息??这是最基本的。与任何侦听器类相同-在第一次加载时创建它们并在scroll上重用它们(根据需要更改任何内部状态)。看我的问题,我在getView方法中添加了viewHolder,但仍然很慢。看我的问题,我在代码中添加了viewHolder,但仍然很慢。我试着在一个简单的列表视图中查看1000个对象,它正在工作。但在可扩展的列表上仍然很慢。我可以做些什么来改进(我提到了使用viewHolder中的collection来保存图像。现在我第一次从服务器加载并在本地保存,然后从文件中获取图像,但正如您所看到的,没有异步任务)现在,我使用了所有同步从模型接收数据的方法。我刚刚开始,我计划在将来使所有的方法都是异步的。你是说在我改变这些方法后,我会得到一个平滑的卷轴??因为如果我在适配器中初始化一个包含1000个对象的列表,而不从模型和服务器获取数据,它看起来很棒!!!如果你的回答是肯定的,你能告诉我是否还有其他问题吗??因为我使用了@Gabe Sechan告诉我的视图持有者,但我不知道我是否正确。您能告诉我如何使用viewHolder加载图像吗?顺便说一句,我来自阿什杜德:)啊,很高兴在这里见面:)。是的,这就是我所说的-网络调用绝对不应该发生在适配器中,它应该发生在之前,下载的内容应该在您将模型传递给适配器之前在模型中。第二,不是所有的方法都应该是异步的,但是像加载图像这样的繁重的方法应该是异步的。你能看一下吗?我添加了活动代码和加载
public void getGroupImage(final String imageName, final LoadImageListener listener) {
AsyncTask<String, String, Bitmap> task = new AsyncTask<String, String, Bitmap>() {
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bmp = loadImageFromFile(imageName); //first try to find the image on the device
// Bitmap bmp = null;
if (bmp == null) { //if image not found - try downloading it from parse
bmp = modelParse.getGroupImage(imageName);
if (bmp != null)
saveImageToFile(bmp, imageName); //save the image locally for next time *****
}
Bitmap scaledBitmap = scaleDown(bmp, 200, true);
return scaledBitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
listener.onResult(result);
}
};
task.execute();
}
private void saveImageToFile(Bitmap imageBitmap, String imageFileName) {
FileOutputStream fos;
OutputStream out = null;
try {
File dir = context.getExternalFilesDir(null);
out = new FileOutputStream(new File(dir, imageFileName + ".jpg"));
imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private Bitmap loadImageFromFile(String fileName) {
Bitmap bitmap = null;
try {
File dir = context.getExternalFilesDir(null);
InputStream inputStream = new FileInputStream(new File(dir, fileName + ".jpg"));
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bitmap;
}