2016-06-08 1 views
2

확장 가능 listview 스크롤이 매우 느리고 child보기까지 부모 카테고리를 클릭 할 때까지 시간이 걸립니다.Expandable ListView가 안드로이드 OS에서 매우 느림

그룹 목록 활동 :

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(); 
    } 
} 

이 내가로드 어댑터의 활동 확장 listview입니다 :

@Override 
    public View getGroupView(final int parent, boolean isExpanded, View convertView, ViewGroup parentView) { 
     final String categoryName = (String)getGroup(parent); 

     ParentViewHolder pHolder = null; 

     if(convertView == null) { 
      pHolder = new ParentViewHolder(); 
      LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      convertView = inflater.inflate(R.layout.expandable_list_parent, parentView, false); 

      // Connect between buttons to layout id 
      pHolder.categoryNameTextView = (TextView) convertView.findViewById(R.id.categoryName); 
      pHolder.editCategory = (ImageButton) convertView.findViewById(R.id.editCategory); 
      pHolder.deleteCategory = (ImageButton) convertView.findViewById(R.id.deleteCategory); 
      convertView.setTag(pHolder); 
     } 
     else { 
      pHolder = (ParentViewHolder) convertView.getTag(); 
     } 

     // Hide edit and delete button for category name Others 
     if(categoriesList.get(parent).equals("Others")){ 
      pHolder.editCategory.setVisibility(View.GONE); 
      pHolder.deleteCategory.setVisibility(View.GONE); 
     } 

     else { 
      pHolder.editCategory.setVisibility(View.VISIBLE); 
      pHolder.deleteCategory.setVisibility(View.VISIBLE); 
     } 

     // Set category name on row 
     pHolder.categoryNameTextView.setTypeface(null, Typeface.BOLD); 
     pHolder.categoryNameTextView.setText(categoryName + ": " + getChildrenCount(parent)); 

     // Set edit category button listener 
     final ParentViewHolder finalPHolder = pHolder; 
     pHolder.editCategory.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       finalPHolder.editCategory.setEnabled(false); 
       editCategoryName(activity, finalPHolder.categoryNameTextView.getText().toString().toString().split(": ")[0], finalPHolder.editCategory, parent); 
      } 
     }); 

     // Set delete category button listener 
     pHolder.deleteCategory.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       finalPHolder.deleteCategory.setEnabled(false); 
       deleteCategory(activity, categoryName, finalPHolder.deleteCategory); 
      } 
     }); 

     return convertView; 
    } 

    @Override 
    public View getChildView(final int parent, final int child, boolean lastChild, View convertView, ViewGroup parentView) { 
     final Group group = (Group)getChild(parent, child); 

     ChildViewHolder cHolder = null; 

     if(convertView == null){ 
      cHolder = new ChildViewHolder(); 
      LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      convertView = inflater.inflate(R.layout.expandable_list_child, parentView, false); 

      // Connect between buttons to layout id 
      cHolder.groupImage = (ImageView) convertView.findViewById(R.id.groupImage); 
      cHolder.groupName = (TextView) convertView.findViewById(R.id.groupName); 
      cHolder.moveCategory = (ImageButton) convertView.findViewById(R.id.moveCategory); 
      cHolder.groupFavoritesButton = (ImageButton) convertView.findViewById(R.id.groupFavorites); 
      cHolder.groupLeaveGroupButton = (Button) convertView.findViewById(R.id.groupLeave); 
      cHolder.groupImageProgressbar = (ProgressBar) convertView.findViewById(R.id.groupImageProgressBar); 
      convertView.setTag(cHolder); 
     } else { 
      cHolder = (ChildViewHolder) convertView.getTag(); 
     } 

     // Set group name on row 
     cHolder.groupName.setText(group.getName()); 

     // Load group image 
     cHolder.groupImageProgressbar.setVisibility(View.VISIBLE); 
     final ChildViewHolder finalHolder = cHolder; 
     Model.getInstance().getGroupImage(group.getImageName(), new Model.LoadImageListener() { 
      @Override 
      public void onResult(Bitmap imageBmp) { 
       finalHolder.groupImage.setImageBitmap(imageBmp); 
       finalHolder.groupImageProgressbar.setVisibility(View.GONE); 
       finalHolder.groupImage.setVisibility(View.VISIBLE); 
      } 
     }); 

     // Set move category button listener 
     cHolder.moveCategory.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       finalHolder.moveCategory.setEnabled(false); 
       showDialogMoveCategory(activity, group.getGroupID(), finalHolder.moveCategory); 
      } 
     }); 


     // After click on group image - open profile for this group 
     cHolder.groupImage.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       onGroupSelected(group.getGroupID()); 
      } 
     }); 

     // Setting favorite Button Image 
     boolean isFavorite = Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID()); 

     if(isFavorite) 
      cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on); 
     else 
      cHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off); 

     // Setting favorite Button Action 
     cHolder.groupFavoritesButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       // Add group to favorites 
       if (!Model.getInstance().groupIsFavorite(loggedUserId, group.getGroupID())) { 
        finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_on); 
        Toast.makeText(activity, 
          "The group " + group.getName() + " was added to favorites", Toast.LENGTH_SHORT).show(); 
        Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "true"); 

       } else { 
        // Delete group from favorites 
        finalHolder.groupFavoritesButton.setBackgroundResource(R.mipmap.favorites_off); 
        Toast.makeText(activity, 
          "The group " + group.getName() + " was removed from favorites", Toast.LENGTH_SHORT).show(); 
        Model.getInstance().changeFavoriteStatus(loggedUserId, group.getGroupID(), "false"); 
       } 
      } 
     }); 

     // After click on group action - leave group 
     cHolder.groupLeaveGroupButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       finalHolder.groupLeaveGroupButton.setEnabled(false); 
       showDialogLeaveGroup(activity, "Are you sure ?", "This action will remove yourself from the group " + group.getName(), group.getGroupID(), parent, child); 
       finalHolder.groupLeaveGroupButton.setEnabled(true); 
      } 
     }); 

     return convertView; 
    } 

로딩 이미지 방법 : 내가 볼

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; 
    } 

답변

0

성능을위한 두 가지 주요 문제.

먼저 findViewById를 모든 곳에서 사용합니다. 당신은 전체 뷰 트리를 따라 걷고 있습니다. 이것이 바로 사람들이 ViewHolder 패턴이나 사용자 정의보기 패턴을 사용하는 이유입니다. 앱의 수명 기간 동안 findViewById를 한 번에 두 번 이상 사용하는 경우 문제가 발생합니다.

둘째, 사용자가 스크롤 할 때마다 새 개체를 할당합니다. 하지마. ViewHolder 또는 vustom 뷰 패턴을 사용하면 모든 스크롤 이벤트에 대해 동일한 OnClickListener를 다시 사용할 수 있으므로 행당 한 번만 만들고 필요에 따라 값을 업데이트 할 수 있습니다. convertView가 널 (null) 일 때를 제외하고 getView에서 오브젝트를 작성하는 경우, 실제로 비효율적입니다.

+0

나는 지금 막 그것에 관하여 빨강, 당신은 능률적 인 지침서를 제공 할 수 있는가? 만약 내가 올바르게한다면, 나는 첫 번째 로딩 할 때 버튼 (findViewById)을 초기화하고 스크롤하기 전에 얻은 "오래된"정보를 사용해야합니까 ?? –

+0

그게 기본이야. 리스너 클래스와 동일합니다. 처음로드 할 때 생성하고 스크롤 할 때 다시 사용합니다 (필요한 경우 내부 상태 변경). –

+0

내 질문을 봐, 내가 viewHolder getView 메서드를 추가했지만 여전히 느린 –

0

잘못된 부분을 스스로 찾아내는 데 도움이되는 listviews 및 어댑터에 대한 일반적인 (추상적 인) 아이디어를 제공하려고합니다.

어댑터의 모든 목적은 해당 목록 항목에 대한 올바른 데이터를 표시하고 가능하면 다른 작업을 수행하는 것입니다.. 이 프로세스에서는 데이터 조작과 관련된 모든 것이 지연과 느린 스크롤을 발생시키는 CPU주기를 필요로합니다.

특히 안드로이드 앱은 초당 60 프레임의 속도로 실행되어야하며 각 프레임은이 60FPS 속도를 달성하기 위해 렌더링하는 데 16.6 밀리 초 이상 걸리지 않아야합니다. 따라서 CPU에 대한 추가로드를 생성하는 경우 프레임 렌더링의 계정에 올 수 있으며 여기에서 렌더링의 지연 경로가 짧습니다.

나는 무엇을 말하고 있는가? 어쩌면 그 자리에서 동 기적으로 데이터를 조작하는 일부 메소드가 어댑터에 있으며 CPU에 과세 대상이 될 수 있습니다. 어댑터는 표시를 위해 준비된 데이터를 표시하고 올바른보기로 표시합니다. 성능 문제의 예는 모든보기마다 매번 String.replace() 메서드를 사용하는 것만 큼 쉽거나 다른 나쁜 예제는 비동기식 대신 동기식으로 이미지를로드합니다.

+0

지금 , 나는 모델로부터 동 기적으로 데이터를받는 모든 메소드를 사용했다.방금 시작했고 앞으로 모든 방법을 비동기식으로 만들 계획입니다. 내가이 방법들을 바꾼 후에 나는 부드러운 두루마리를 얻을 것이라고 말하는거야 ?? becuase 모델과 서버에서 데이터를 가져 오지 않고 어댑터의 1000 개체 목록을 초기화하면 멋지게 보입니다. 대답이 '예'라면 다른 문제가 있는지 말할 수 있습니까 ?? @Gabe Sechan이 viewHolder를 사용했기 때문에 Sechan이 나에게 말했기 때문에 올바르게 처리했는지는 알 수 없습니다. 그리고 viewHolder로 이미지를로드하는 방법을 알려주시겠습니까? Btw 나는 ashdod 출신이다 :) –

+0

Ahah 만나서 반가워 :). 네, 그게 제가 말한 바입니다 .- 네트워크 콜은 어댑터에서 절대 일어나지 않아야합니다. 어댑터에 모델을 전달하기 전에 다운로드 된 것이 무엇이든 모델 내부에 있어야합니다. 둘째 - 모든 방법이 비동기 적이어야하는 것은 아니지만 이미지를로드하는 것과 같은 무거운 것들은 – Yosi199

+0

수 있습니까? 내 DataProvider와 비동기 적으로 데이터를로드하는 작업 코드 및로드 이미지 메서드를 추가했습니다. 그런 다음 어댑터를 초기화합니다. 어댑터에서 정보를 가져 와서 뷰를 업데이트 할 때까지는 응용 프로그램에서 회 전자를 볼 수 있습니다 (이전보다 더 유동적이었습니다. 데이터가 표시 될 때까지 검은 색 화면이있었습니다). 그러나 스크롤을하거나 범주를 확장하려고 할 때 유체 목록이 없습니다. 도와 줄 수 있고 무엇을 바꾸라고 말해 줄 수 있습니까? 어쩌면 이미지 나 다른 것입니다 ... –