Vudroid is a reader that can read PDF and djvu formats, it features page jumping, full-screen reading, continuous caching of two pages at a time, free page scaling, memory reading location, and smooth page swiping. The disadvantage is that when the file is large, rendering speed is slow and occasionally exits.
In the process of analyzing the vudroid source code, we divide it into three parts:
1) operate the main interface (select a file and browse and read the history part)
2) reading interface (involving reading operations and event handling)
3) Source File Parsing (mainly reading PDF and djvu files, which involves parsing the format of related files)
1. Main Interface
The main interface of vudroid is relatively simple. It mainly includes a file browsing interface and a history browsing interface, which is implemented using a tabhost. The interface is as follows:
Browse the sdcard folder to display the file directories. Only PDF, djvu, and djv files are displayed for files. Recent displays the list of recently viewed files.
The implementation of this part is relatively simple, mainly including three files:
1. mainbrowseractivity
Let's take a look at the source code of mainbrowseractivity. Java:
public class MainBrowserActivity extends BaseBrowserActivity{ private final static HashMap<String, Class<? extends Activity>> extensionToActivity = new HashMap<String, Class<? extends Activity>>(); static { extensionToActivity.put("pdf", PdfViewerActivity.class); extensionToActivity.put("djvu", DjvuViewerActivity.class); extensionToActivity.put("djv", DjvuViewerActivity.class); } @Override protected FileFilter createFileFilter() { return new FileFilter() { public boolean accept(File pathname) { for (String s : extensionToActivity.keySet()) { if (pathname.getName().endsWith("." + s)) return true; } return pathname.isDirectory(); } }; } @Override protected void showDocument(Uri uri) { final Intent intent = new Intent(Intent.ACTION_VIEW, uri); String uriString = uri.toString(); String extension = uriString.substring(uriString.lastIndexOf('.') + 1); intent.setClass(this, extensionToActivity.get(extension)); startActivity(intent); }}
Here we leave all interface-related things to basebrowseractivity. java. Mainbrowseractivity is only responsible for judging readable files and forwarding them to related processing classes for processing. Mainbrowseractivity reloads the createfilefilter function to create a file filter. This is actually a decoupling process. In the future, you need to add files in other formats, such as txt, you only need to modify the createfilefilter function, and there is no need to change the code in basebrowseractivity.
2. basebrowseractivity
For the basebrowseractivity file, you can actually guess its function: generate two listview to fill the tabhost. So let's look at its oncreate function:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.browser); viewerPreferences = new ViewerPreferences(this); final ListView browseList = initBrowserListView(); final ListView recentListView = initRecentListView(); TabHost tabHost = (TabHost) findViewById(R.id.browserTabHost); tabHost.setup(); tabHost.addTab(tabHost.newTabSpec("Browse").setIndicator("Browse").setContent(new TabHost.TabContentFactory() { public View createTabContent(String s) { return browseList; } })); tabHost.addTab(tabHost.newTabSpec("Recent").setIndicator("Recent").setContent(new TabHost.TabContentFactory() { public View createTabContent(String s) { return recentListView; } })); }
Here, the two tabs of tabhost: The first browse, need to display the current file directory, click the folder to display the next folder directory, and click the file to go to the activity that browses the file; for the second recent, you only need to click the file and go to the activity that browses the file. As we can see, the setonitemclicklistener of the two tabs is different.
For browse:
private ListView initBrowserListView() { final ListView listView = new ListView(this); adapter = new BrowserAdapter(this, filter); listView.setAdapter(adapter); listView.setOnItemClickListener(onItemClickListener); listView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); return listView; }
private final AdapterView.OnItemClickListener onItemClickListener = new AdapterView.OnItemClickListener() { @SuppressWarnings({"unchecked"}) public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { final File file = ((AdapterView<BrowserAdapter>)adapterView).getAdapter().getItem(i); if (file.isDirectory()) { setCurrentDir(file); } else { showDocument(file); } } };
For recent:
private ListView initRecentListView() { ListView listView = new ListView(this); recentAdapter = new UriBrowserAdapter(); listView.setAdapter(recentAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @SuppressWarnings({"unchecked"}) public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { showDocument(((AdapterView<UriBrowserAdapter>) adapterView).getAdapter().getItem(i)); } }); listView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); return listView; }
From the source code, we can see that the difference between the two is obvious.
3. viewerpreferences
The viewerpreferences file uses sharedpreferences to store two parameters: full screen and recently viewed file list.
For recently viewed files, the code is:
public void addRecent(Uri uri) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("recent:" + uri.toString(), uri.toString() + "\n" + System.currentTimeMillis()); editor.commit(); } public List<Uri> getRecent() { TreeMap<Long, Uri> treeMap = new TreeMap<Long, Uri>(); for (String key : sharedPreferences.getAll().keySet()) { if (key.startsWith("recent")) { String uriPlusDate = sharedPreferences.getString(key, null); String[] uriThenDate = uriPlusDate.split("\n"); treeMap.put(Long.parseLong(uriThenDate.length > 1 ? uriThenDate[1] : "0"), Uri.parse(uriThenDate[0])); } } ArrayList<Uri> list = new ArrayList<Uri>(treeMap.values()); Collections.reverse(list); return list; }
4. Adapter
The two adapters used in the project are reloaded: browseradapter and uribrowseradapter.
The role of the adapter is to connect view and dataset. One of the most important functions is getview. A view is generated for a piece of data in each dataset. This view is obtained through this function. Therefore, if you want a view to display your data, you must reload this function.
public View getView(int i, View view, ViewGroup viewGroup) { final View browserItem = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.browseritem, viewGroup, false); final ImageView imageView = (ImageView) browserItem.findViewById(R.id.browserItemIcon); final Uri uri = uris.get(i); final TextView textView = (TextView) browserItem.findViewById(R.id.browserItemText); textView.setText(uri.getLastPathSegment()); imageView.setImageResource(R.drawable.book); return browserItem; }
In addition, when the dataset of the adapter changes, you need to notify the view to change the data through the yydatasetinvalidated () function.
public void setUris(List<Uri> uris) { this.uris = uris; notifyDataSetInvalidated(); }