I'm using a custom Collapsing Toolbar Layout, which has a Title and a Subtitle.
I got the title to collapse and animate on a curved path, but the part of the title becoming smaller as collapsing isn't smooth. It resizes in a jagged sort of way.
This is my behavior that is responsible for moving and resizing the title:
public class ViewBehavior : CoordinatorLayout.Behavior
{
private Context mContext;
private int mStartMarginRight;
private int mEndMargintRight;
private int mMarginLeft;
private int mStartMarginBottom;
private bool isHide;
private static float SCALE_MINIMUM = 0.5f;
public ViewBehavior(Context context, IAttributeSet attrs)
{
mContext = context;
}
public override bool LayoutDependsOn(CoordinatorLayout parent, Java.Lang.Object child, View dependency)
{
return dependency is AppBarLayout;
}
public override bool OnDependentViewChanged(CoordinatorLayout parent, Java.Lang.Object child, View dependency)
{
ShouldInitProperties((child as HeaderView), dependency);
int maxScroll = ((AppBarLayout)dependency).TotalScrollRange;
float percentage = System.Math.Abs(dependency.GetY()) / (float)maxScroll;
float childPosition = dependency.Height
+ dependency.GetY()
- (child as View).Height
- (getToolbarHeight() - (child as View).Height) * percentage / 2;
childPosition = childPosition - mStartMarginBottom * (1f - percentage);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)(child as View).LayoutParameters;
lp.RightMargin = (int)(100 * System.Math.Sin(percentage * System.Math.PI)) + mStartMarginRight / 2 + mEndMargintRight / 2;
lp.LeftMargin = mMarginLeft;
(child as View).LayoutParameters = lp;
(child as View).SetY(childPosition);
float x = (child as HeaderView).Title.TextSize;
//Here is the algorithm for setting the text size
(child as HeaderView).Title.SetTextSize(ComplexUnitType.Sp, 36 * (1 - percentage / 2));
(child as HeaderView).SubTitle.SetTextSize(ComplexUnitType.Sp, 26 * (1 - percentage / 2));
var toolbarTitleSize = (int)TypedValue.ApplyDimension(ComplexUnitType.Sp, 18, Application.Context.Resources.DisplayMetrics);
var toolbarSubTitleSize = (int)TypedValue.ApplyDimension(ComplexUnitType.Sp, 16, Application.Context.Resources.DisplayMetrics);
if ((child as HeaderView).Title.TextSize < toolbarTitleSize)
(child as HeaderView).Title.SetTextSize(ComplexUnitType.Sp, 18);
if ((child as HeaderView).SubTitle.TextSize < toolbarSubTitleSize)
(child as HeaderView).SubTitle.SetTextSize(ComplexUnitType.Sp, 14);
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
if (isHide && percentage < 1)
{
(child as View).Visibility = ViewStates.Visible;
isHide = false;
}
else if (!isHide && percentage == 1)
{
(child as View).Visibility = ViewStates.Gone;
isHide = true;
}
}
return true;
}
public void ShouldInitProperties(HeaderView child, View dependency)
{
if (mStartMarginRight == 0)
mStartMarginRight = mContext.Resources.GetDimensionPixelOffset(Resource.Dimension.header_view_start_margin_right);
if (mEndMargintRight == 0)
mEndMargintRight = mContext.Resources.GetDimensionPixelOffset(Resource.Dimension.header_view_end_margin_right);
if (mStartMarginBottom == 0)
mStartMarginBottom = mContext.Resources.GetDimensionPixelOffset(Resource.Dimension.header_view_start_margin_bottom);
if (mMarginLeft == 0)
mMarginLeft = mContext.Resources.GetDimensionPixelOffset(Resource.Dimension.header_view_end_margin_left);
}
public int getToolbarHeight()
{
int result = 0;
TypedValue tv = new TypedValue();
if (mContext.Theme.ResolveAttribute(Android.Resource.Attribute.ActionBarSize, tv, true))
{
result = TypedValue.ComplexToDimensionPixelSize(tv.Data, mContext.Resources.DisplayMetrics);
}
return result;
}
}
How can I change the algorithm so it should resize in a smoother fashion?
Edit - Video:
https://youtu.be/j6LseSW6h1s
As mentioned by others scaling via textSize doesn't work well on Android within animations since it isn't accurate enough (it rounds up the decimal values to integers).
If it fulfills your need you should perform your animation with the scaleX/scaleY attributes, e.g.:
float scale = 1 - percentage * SCALE_MINIMUM;
(child as HeaderView).Title.SetScaleX(scale);
(child as HeaderView).Title.SetScaleY(scale);
(child as HeaderView).SubTitle.SetScaleX(scale);
(child as HeaderView).SubTitle.SetScaleY(scale);
The problem you have is that the even if you calculate scales as demical values, they become integer values in the TextView. You should enable both LINEAR_TEXT_FLAG and SUBPIXEL_TEXT_FLAG flags in your TextView's Paint class to achieve smooth scaling and positioning.
Something like this:
yourTextView.Paint.SubpixelText = true;
yourTextView.Paint.LinearText = true;
I have use this layout format for collapsing toolbar and it works smoothly.
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="#dimen/height_300dp"
android:background="?colorPrimary">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsible_Toolbar_Layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:collapsedTitleTextAppearance="#style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
app:contentScrim="#color/colorDarkBlue"
app:expandedTitleMarginEnd="#dimen/margin_64dp"
app:expandedTitleMarginStart="#dimen/margin_20dp"
app:expandedTitleTextAppearance="#style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:fitsSystemWindows="true"
android:focusableInTouchMode="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorTransparent" />
<ImageButton
android:id="#+id/ib"
android:layout_width="#dimen/margin_35dp"
android:layout_height="#dimen/margin_35dp"
android:layout_gravity="right"
android:layout_marginRight="#dimen/margin_10dp"
android:layout_marginTop="#dimen/height_245dp"
android:background="#null"
android:src="#drawable/ic_launcher" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#android:color/transparent"
android:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:contentInsetStart="#dimen/margin_50dp"
app:layout_collapseMode="pin">
<ImageButton
android:id="#+id/ib2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="#dimen/margin_10dp"
android:background="#null"
android:popupTheme="#style/ThemeOverlay.AppCompat.Light"
android:src="#drawable/ic_launcher"
android:visibility="invisible" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
After that in Fragment in IntializeComponents I am intialize this layout only and set Text
appBarLayout = (AppBarLayout)view.findViewById(R.id.app_bar_layout);
CollapsingToolbarLayout toolbarLayout = (CollapsingToolbarLayout) view.findViewById(R.id.collapsible_Toolbar_Layout);
toolbarLayout.setTitle(yourTitle);
toolbarLayout.setCollapsedTitleTextColor(Color.WHITE);
toolbarLayout.setExpandedTitleColor(Color.WHITE);
appBarLayout.addOnOffsetChangedListener(this);
Add this method for Handle the toolbar
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
int maxScroll = appBarLayout.getTotalScrollRange();
float percentage = (float) Math.abs(offset) / (float) maxScroll;
handleToolbarTitleVisibility(percentage);
}
private void handleToolbarTitleVisibility(float percentage) {
if (percentage >= PERCENTAGE_TO_SHOW_TITLE_AT_TOOLBAR) {
if(!mIsTheTitleVisible) {
imageButton.setVisibility(View.VISIBLE);
ib.setVisibility(View.INVISIBLE);
mIsTheTitleVisible = true;
}
} else {
if (mIsTheTitleVisible) {
imageButton.setVisibility(View.INVISIBLE);
ibMap.setVisibility(View.VISIBLE);
mIsTheTitleVisible = false;
}
}
}
I hope it will helps you :)
No if you are using customTextview then also it will work because I am also using customTextView only.
Will you post the customtextview code here? So all can see the problem in your customTextview.
Related
I have a very simple activity with a vertical scroll view (mainscreen.xml below).
This vertical scroll view has a linear layout child view.
I dynamically add text views as children to this linear layout (populateLinlayWithTextViews() below).
Later, after these text view children have been added, I can scroll the vertical scroll view (onScrollChange() below).
And I can measure the visible part of the vertical scroll view (getHeight()).
My problem is that I need to know earlier than at scroll time what is the maximum height of the visible part of the vertical scroll view.
mainscreen.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="#+id/scrollview"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<LinearLayout
android:id="#+id/linlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</LinearLayout>
MyActivity.java
protected void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.mainscreen);
Resources resources = getResources();
int n_statusBarHeight = resources.getIdentifier( "status_bar_height", "dimen", "android"); // 36px
populateLinlayWithTextViews();
// HERE
ScrollView sv = sv = findViewById(R.id.scrollview);
int n_svVisiblePartHeight = sv.getHeight(); // 0px
View.OnScrollChangeListener onScrollChangedListener
= new View.OnScrollChangeListener()
{
#Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY)
{
ScrollView sv = sv = findViewById(R.id.scrollview);
int n_svVisiblePartHeight = sv.getHeight(); // 680px
}
}
sv = findViewById(R.id.scrollview);
sv.setOnScrollChangeListener(onScrollChangedListener);
}
public void populateLinlayWithTextViews()
{
LinearLayout linlay = findViewById(R.id.linlay);
int n_viewHeight = 81; // px
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
n_viewHeight);
for(int n_i=0 ; n_i<20 ; n_i++)
{
TextView tvNew = new TextView(m_app);
tvNew.setText(n_i + " - test");
tvNew.setTextColor(Color.rgb(0, 0, 0)); // black
tvNew.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40);
tvNew.setLayoutParams(params);
linlay.addView(tvNew, linlay.getChildCount());
}
}
When I scroll the vertical scroll view, I can see that n_svVisiblePartHeight's value is 680px.
But I need to know this value earlier // HERE.
But // HERE, the value I get is 0px.
Can you help me?
EDIT 1
I need to know this height because:
example 1: I would like to know initially the index of the text view that will be at the bottom of the (visible) screen after I have populated
the linear layout with text views ;
example 2: given this height X I'm looking for, let's say that I would like 10 text views to be visible initially, I would like to determine X/10 which will be the height of the text views, so that I can put it dynamically in n_viewHeight.
EDIT 2
I just need to know the height of the red arrow below, a soon as I can in the lifecycle of the activity and preferably before I add text views to the linear layout or right after (// HERE):
EDIT 3
I don't know if theoretically that's true:
n_scrollViewVisibleHeightInPx =
n_displayHeightInPx
- n_actionBarHeight
- n_navigationBarHeightInPx
+ n_statusBarHeightInPx;
... but practically n_scrollViewVisibleHeightInPx is the height of the space designated by the red arrow in the image above.
To get these various heights, below are the methods I used:
private int returnStatusBarHeightInPx()
{
int n_statusBarHeightInPx = -1;
Resources resources = null;
int n_idStatusBarHeight = -1;
resources = getResources();
n_idStatusBarHeight = resources.getIdentifier( "status_bar_height", "dimen", "android");
if(n_idStatusBarHeight > 0)
{
n_statusBarHeightInPx = getResources().getDimensionPixelSize(n_idStatusBarHeight);
}
else
{
n_statusBarHeightInPx = 0;
}
return n_statusBarHeightInPx;
}
private int returnNavigationBarHeightInPx()
{
int n_navigationBarHeightInPx = -1;
Resources resources = null;
int n_idNavigationBarHeight = -1;
resources = getResources();
n_idNavigationBarHeight = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if(n_idNavigationBarHeight > 0)
{
n_navigationBarHeightInPx = getResources().getDimensionPixelSize(n_idNavigationBarHeight);
}
else
{
n_navigationBarHeightInPx = 0;
}
return n_navigationBarHeightInPx;
}
private int returnActionBarHeightInPx()
{
TypedValue typedValue = null;
int n_actionBarHeightInPx = -1;
typedValue = new TypedValue();
if(getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true))
{
n_actionBarHeightInPx =
TypedValue.complexToDimensionPixelSize(
typedValue.data,
getResources().getDisplayMetrics());
}
return n_actionBarHeightInPx;
}
private int returnDisplayHeightInPx()
{
DisplayMetrics displayMetrics = null;
int n_displayHeightInPx = -1;
displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
n_displayHeightInPx = displayMetrics.heightPixels;
return n_displayHeightInPx;
}
Why do you want to know it in "//here". You do not use that variable there. Your code does not explain, why it is not possible, to define the variable on scroll time.
I am trying to use PdfRenderer, and the requirement is to have Zoom and scroll available with it, but in Android PdfRenderer do not provide any support for Zoom and scroll, there is only page navigation support available.
But i guess zoom and scroll support can be implemented as PdfRenderer uses bitmap to show the content using imageview.
How to implement Zoom and Scroll support with Google PdfRenderer
sample?
PS: I am using this PdfRenderer sample provided by Google, https://github.com/googlesamples/android-PdfRendererBasic
I used the idea of #yan-yankelevich and wrote the code in Java. Much problem was with finding proper zoom and corresponding Bitmap size values. Don't forget that PdfRenderer works only on API 21+.
Fragment with PDF Bitmap fragment_pdf_renderer.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:orientation="vertical"
tools:context=".PdfRendererFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:contentDescription="#null" />
</HorizontalScrollView>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/from_divider_gray"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="#+id/previous"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/previous_page"
android:textSize="13sp" />
<Button
android:id="#+id/next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/next_page"
android:textSize="13sp" />
<ImageButton
android:id="#+id/zoomout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:padding="8dp"
android:src="#drawable/ic_zoom_out_black_36dp" />
<ImageButton
android:id="#+id/zoomin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:padding="8dp"
android:src="#drawable/ic_zoom_in_black_36dp" />
</LinearLayout>
</LinearLayout>
The PdfRendererFragment:
/**
* This fragment has a big {#ImageView} that shows PDF pages, and 2
* {#link android.widget.Button}s to move between pages. We use a
* {#link android.graphics.pdf.PdfRenderer} to render PDF pages as
* {#link android.graphics.Bitmap}s.
*/
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class PdfRendererFragment extends Fragment implements View.OnClickListener {
/**
* Key string for saving the state of current page index.
*/
private static final String STATE_CURRENT_PAGE_INDEX = "current_page_index";
/**
* The filename of the PDF.
*/
public String FILENAME;
public String PURCHASE_ID;
public int TICKETS_NUMBER;
/**
* File descriptor of the PDF.
*/
private ParcelFileDescriptor mFileDescriptor;
/**
* {#link android.graphics.pdf.PdfRenderer} to render the PDF.
*/
private PdfRenderer mPdfRenderer;
/**
* Page that is currently shown on the screen.
*/
private PdfRenderer.Page mCurrentPage;
/**
* {#link android.widget.ImageView} that shows a PDF page as a {#link android.graphics.Bitmap}
*/
private ImageView mImageView;
/**
* {#link android.widget.Button} to move to the previous page.
*/
private Button mButtonPrevious;
private ImageView mButtonZoomin;
private ImageView mButtonZoomout;
private Button mButtonNext;
private float currentZoomLevel = 12;
/**
* PDF page index
*/
private int mPageIndex;
public PdfRendererFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_pdf_renderer, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Retain view references.
mImageView = (ImageView) view.findViewById(R.id.image);
mButtonPrevious = (Button) view.findViewById(R.id.previous);
mButtonNext = (Button) view.findViewById(R.id.next);
mButtonZoomin = view.findViewById(R.id.zoomin);
mButtonZoomout = view.findViewById(R.id.zoomout);
// Bind events.
mButtonPrevious.setOnClickListener(this);
mButtonNext.setOnClickListener(this);
mButtonZoomin.setOnClickListener(this);
mButtonZoomout.setOnClickListener(this);
mPageIndex = 0;
// If there is a savedInstanceState (screen orientations, etc.), we restore the page index.
if (null != savedInstanceState) {
mPageIndex = savedInstanceState.getInt(STATE_CURRENT_PAGE_INDEX, 0);
}
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FILENAME = getActivity().getIntent().getExtras().getString("pdfFilename");
TICKETS_NUMBER = getActivity().getIntent().getExtras().getInt("tickets_number");
PURCHASE_ID = getActivity().getIntent().getExtras().getString("purchaseGuid");
}
#Override
public void onStart() {
super.onStart();
try {
openRenderer(getActivity());
showPage(mPageIndex);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getActivity(), getString(R.string.ticket_file_not_found, FILENAME), Toast.LENGTH_SHORT).show();
App app = (App) getActivity().getApplicationContext();
TicketUtil.downloadTicket(app, PURCHASE_ID);
getActivity().finish();
}
}
#Override
public void onStop() {
try {
closeRenderer();
} catch (IOException e) {
e.printStackTrace();
}
super.onStop();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (null != mCurrentPage) {
outState.putInt(STATE_CURRENT_PAGE_INDEX, mCurrentPage.getIndex());
}
}
/**
* Sets up a {#link android.graphics.pdf.PdfRenderer} and related resources.
*/
private void openRenderer(Context context) throws IOException {
// In this sample, we read a PDF from the assets directory.
File file = TicketUtil.getTicketFile(context, PURCHASE_ID);
if (!file.exists()) {
// Since PdfRenderer cannot handle the compressed asset file directly, we copy it into
// the cache directory.
InputStream asset = context.getAssets().open(FILENAME);
FileOutputStream output = new FileOutputStream(file);
final byte[] buffer = new byte[1024];
int size;
while ((size = asset.read(buffer)) != -1) {
output.write(buffer, 0, size);
}
asset.close();
output.close();
}
mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
// This is the PdfRenderer we use to render the PDF.
if (mFileDescriptor != null) {
mPdfRenderer = new PdfRenderer(mFileDescriptor);
}
}
/**
* Closes the {#link android.graphics.pdf.PdfRenderer} and related resources.
*
* #throws java.io.IOException When the PDF file cannot be closed.
*/
private void closeRenderer() throws IOException {
if (null != mCurrentPage) {
mCurrentPage.close();
mCurrentPage = null;
}
if (null != mPdfRenderer) {
mPdfRenderer.close();
}
if (null != mFileDescriptor) {
mFileDescriptor.close();
}
}
/**
* Zoom level for zoom matrix depends on screen density (dpiAdjustedZoomLevel), but width and height of bitmap depends only on pixel size and don't depend on DPI
* Shows the specified page of PDF to the screen.
*
* #param index The page index.
*/
private void showPage(int index) {
if (mPdfRenderer.getPageCount() <= index) {
return;
}
// Make sure to close the current page before opening another one.
if (null != mCurrentPage) {
mCurrentPage.close();
}
// Use `openPage` to open a specific page in PDF.
mCurrentPage = mPdfRenderer.openPage(index);
// Important: the destination bitmap must be ARGB (not RGB).
int newWidth = (int) (getResources().getDisplayMetrics().widthPixels * mCurrentPage.getWidth() / 72 * currentZoomLevel / 40);
int newHeight = (int) (getResources().getDisplayMetrics().heightPixels * mCurrentPage.getHeight() / 72 * currentZoomLevel / 64);
Bitmap bitmap = Bitmap.createBitmap(
newWidth,
newHeight,
Bitmap.Config.ARGB_8888);
Matrix matrix = new Matrix();
float dpiAdjustedZoomLevel = currentZoomLevel * DisplayMetrics.DENSITY_MEDIUM / getResources().getDisplayMetrics().densityDpi;
matrix.setScale(dpiAdjustedZoomLevel, dpiAdjustedZoomLevel);
// Toast.makeText(getActivity(), "width " + String.valueOf(newWidth) + " widthPixels " + getResources().getDisplayMetrics().widthPixels, Toast.LENGTH_LONG).show();
// matrix.postTranslate(-rect.left/mCurrentPage.getWidth(), -rect.top/mCurrentPage.getHeight());
// Here, we render the page onto the Bitmap.
// To render a portion of the page, use the second and third parameter. Pass nulls to get
// the default result.
// Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter.
mCurrentPage.render(bitmap, null, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
// We are ready to show the Bitmap to user.
mImageView.setImageBitmap(bitmap);
updateUi();
}
/**
* Updates the state of 2 control buttons in response to the current page index.
*/
private void updateUi() {
int index = mCurrentPage.getIndex();
int pageCount = mPdfRenderer.getPageCount();
if (pageCount == 1) {
mButtonPrevious.setVisibility(View.GONE);
mButtonNext.setVisibility(View.GONE);
} else {
mButtonPrevious.setEnabled(0 != index);
mButtonNext.setEnabled(index + 1 < pageCount);
}
if (currentZoomLevel == 2) {
mButtonZoomout.setActivated(false);
} else {
mButtonZoomout.setActivated(true);
}
}
/**
* Gets the number of pages in the PDF. This method is marked as public for testing.
*
* #return The number of pages.
*/
public int getPageCount() {
return mPdfRenderer.getPageCount();
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.previous: {
// Move to the previous page
currentZoomLevel = 12;
showPage(mCurrentPage.getIndex() - 1);
break;
}
case R.id.next: {
// Move to the next page
currentZoomLevel = 12;
showPage(mCurrentPage.getIndex() + 1);
break;
}
case R.id.zoomout: {
// Move to the next page
--currentZoomLevel;
showPage(mCurrentPage.getIndex());
break;
}
case R.id.zoomin: {
// Move to the next page
++currentZoomLevel;
showPage(mCurrentPage.getIndex());
break;
}
}
}
}
Bring your attention to the fact that zoom level depends on your screen density, but width and height of Bitmap (it is in pixels) depend only on your zoom level. Also, you need to tweak your sizes so that at default zoom (for me it was pdf rendered full screen and value was 12) you PDF bitmap takes no more and no less than needed in your View.
int newWidth = (int) (getResources().getDisplayMetrics().widthPixels * mCurrentPage.getWidth() / 72 * currentZoomLevel / 40);
int newHeight = (int) (getResources().getDisplayMetrics().heightPixels * mCurrentPage.getHeight() / 72 * currentZoomLevel / 64);
Bitmap bitmap = Bitmap.createBitmap(
newWidth,
newHeight,
Bitmap.Config.ARGB_8888);
I found out that zoom 12 fits my screen and 40 and 64 are coefficents that make Bitmap proper size.
mCurrentPage.getWidth() returns width in Postscript points, where each pt is 1/72 inch.
72 (DPI) is the default PDF resolution.
PS. If you need simultaneous vertical and horizontal scroll Scrollview vertical and horizontal in android
The solution i used when confronted to this situation was :
Load the pdfRenderer page in a ImageView
Put my ImageView in a ScrollView (tadam scroll managed), and this ScrollView in a FrameLayout
Add two buttons (outside the scroll view) to manage zoom in and out (each button triggering a scale animation on my ImageView). You could also manage it with a gesture detector but i had hard time with the scrolling behaviour when doing so
Add two buttons to manage page changes (still outside the ScrollView)
For a nice effect i added FadeIn/FadeOut animations on my buttons, FadeIn triggering on OnTouchEvents (if no animation is playing), and FadeOut triggering when FadeIn animation is over
Hope i helped, aks me if you need more detailed informations, but you should know where to start now
Here is a code sample (wich do not inclue page navigation etc, but only zoom behaviour and scrolling, as the rest being in the google code sample you linked)
Code :
C# (but very easy to convert into Java)
private Button _zoomInButton;
private Button _zoomOutButton;
private ImageView _pdfViewContainer;
private float _currentZoomLevel;
private float _zoomFactor;
private float _maxZoomLevel;
private float _minZoomLevel;
private void Init(View view) // the content of this method must go in your OnViewCreated method, here the view being the frameLayout you will find in xml
{
_zoomInButton = view.FindViewById<Button>(Resource.Id.PdfZoomInButton);
_zoomOutButton = view.FindViewById<Button>(Resource.Id.PdfZoomOutButton);
_pdfViewContainer = view.FindViewById<ImageView>(Resource.Id.PdfViewContainer);
_zoomInButton.Click += delegate { ZoomIn(); }; //for you (in Java) this must looks like setOnClickListener(this); and in the onClick metghod you just have to add a case for R.id.PdfZoomInButton containing a call to ZoomIn();
_zoomOutButton.Click += delegate { ZoomOut(); };
_minZoomLevel = 0.9f;
_maxZoomLevel = 1.2f;
_zoomFactor = 0.1f;
}
private void ZoomIn()
{
if (_currentZoomLevel + _zoomFactor < _maxZoomLevel)
{
ScaleAnimation scale = new ScaleAnimation(_currentZoomLevel, _currentZoomLevel + _zoomFactor, _currentZoomLevel, _currentZoomLevel + _zoomFactor, Dimension.RelativeToSelf, 0.5f, Dimension.RelativeToSelf, 0.5f);
scale.Duration = 50;
scale.FillAfter = true;
_pdfViewContainer.StartAnimation(scale);
_currentZoomLevel += _zoomFactor;
}
}
private void ZoomOut()
{
if (_currentZoomLevel - _zoomFactor > _minZoomLevel)
{
ScaleAnimation scale = new ScaleAnimation(_currentZoomLevel, _currentZoomLevel - _zoomFactor, _currentZoomLevel, _currentZoomLevel - _zoomFactor, Dimension.RelativeToSelf, 0.5f, Dimension.RelativeToSelf, 0.5f);
scale.Duration = 50;
scale.FillAfter = true;
_pdfViewContainer.StartAnimation(scale);
_currentZoomLevel -= _zoomFactor;
}
}
XMl
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/PdfContainer">
<ScrollView xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:id="#+id/PdfScrollView">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:scrollbars="vertical"
android:src="#drawable/mediaIconPDF"
android:id="#+id/PdfViewContainer" />
</ScrollView>
<LinearLayout
android:id="#+id/PdfRightLayout"
android:layout_gravity="right"
android:orientation="vertical"
android:gravity="center"
android:layout_width="50dp"
android:layout_height="match_parent"
android:weightSum="1">
<Button
android:id="#+id/PdfZoomInButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="+" />
<space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2" />
<Button
android:id="#+id/PdfZoomOutButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="-" />
</LinearLayout>
<LinearLayout
android:id="#+id/PdfBottomLayout"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/vogofTransparentGrey"
android:weightSum="1">
<Button
android:id="#+id/PdfPreviousPage"
android:layout_width="0dp"
android:layout_weight="0.15"
android:layout_height="match_parent"
android:text="Prev" />
<TextView
android:id="#+id/PdfCurrentPageLabel"
android:layout_width="0dp"
android:layout_weight="0.7"
android:gravity="center"
android:layout_height="match_parent"
/>
<Button
android:id="#+id/PdfNextPage"
android:layout_width="0dp"
android:layout_weight="0.15"
android:layout_height="match_parent"
android:text="Next" />
</LinearLayout>
</FrameLayout>
With this, some time to understand it and little efforts you should be able to get the desired result. Have a nice day
I found a nicer answer here: PdfRendering zoom on page linked by CommonsWare: https://github.com/commonsguy/cw-omnibus/tree/v8.8/PDF/PdfRenderer . So based on soshial's answer, you have pinch zoom and can get rid of the zoom buttons and the constants:
import com.davemorrissey.labs.subscaleview.ImageSource;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
....
private void showPage(int index) {
if (mPdfRenderer.getPageCount() <= index) {
return;
}
// Make sure to close the current page before opening another one.
if (null != mCurrentPage) {
mCurrentPage.close();
}
// Use `openPage` to open a specific page in PDF.
mCurrentPage = mPdfRenderer.openPage(index);
if(mBitmap==null) {
// Important: the destination bitmap must be ARGB (not RGB).
int newWidth = (int) (getResources().getDisplayMetrics().densityDpi * mCurrentPage.getWidth() / 72);
int newHeight = (int) (getResources().getDisplayMetrics().densityDpi * mCurrentPage.getHeight() / 72);
mBitmap = Bitmap.createBitmap(
newWidth,
newHeight,
Bitmap.Config.ARGB_8888);
}
mBitmap.eraseColor(0xFFFFFFFF);
mCurrentPage.render(mBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
// We are ready to show the Bitmap to user.
mSubsamplingImageView.resetScaleAndCenter();
mSubsamplingImageView.setImage(ImageSource.cachedBitmap(mBitmap));
updateUi();
}
I also added bitmap recycle:
/**
* Closes the {#link android.graphics.pdf.PdfRenderer} and related resources.
*
* #throws java.io.IOException When the PDF file cannot be closed.
*/
private void closeRenderer() throws IOException {
if (null != mCurrentPage) {
mCurrentPage.close();
mCurrentPage = null;
}
if (null != mPdfRenderer) {
mPdfRenderer.close();
}
if (null != mFileDescriptor) {
mFileDescriptor.close();
}
if(mBitmap!=null)
{
mBitmap.recycle();
mBitmap = null;
}
}
And in the xml, instead of ImageView:
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
android:id="#+id/report_viewer_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Starting with this solution : https://stackoverflow.com/a/46002017/5049286 I found a good way to avoid the initial zoom coefficient and the others fixed coefficients, only changed this method :
private void showPage(int index) {
if (mPdfRenderer.getPageCount() <= index) {
return;
}
if (null != mCurrentPage) {
mCurrentPage.close();
}
mCurrentPage = mPdfRenderer.openPage(index);
int newWidth = (int) (mVerticalScrollView.getWidth() *
currentZoomLevel);
int newHeight = (int) (newWidth *
((float)mCurrentPage.getHeight()/(float)mCurrentPage.getWidth()));
Bitmap bitmap = Bitmap.createBitmap(
newWidth,
newHeight,
Bitmap.Config.ARGB_8888);
mCurrentPage.render(bitmap, null, null,
PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
mImageView.setImageBitmap(bitmap);
updateUi();
}
With this solution currentZoomLevel starts from 1.0f to xxx ( you decide a limit ) and the rendered image at zoom 1.0f fit into the scrollview and the proportions are maintained...
So, I have a viewflipper which is filled with several webviews. I've then extended the WebView class in order to catch the motions for the viewflipper.
The problem is that when I swipe, the app crashes and gives me the following error:
10-09 17:23:14.443: E/MessageQueue-JNI(21126): java.lang.NullPointerException: Attempt to invoke virtual method 'int android.widget.ViewFlipper.getDisplayedChild()' on a null object reference
10-09 17:23:14.443: E/MessageQueue-JNI(21126): at com.test.hamnarbetare.CustomWebView.onTouchEvent(CustomWebView.java:55)
The code can be viewed here:
package com.test.hamnarbetare;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.webkit.WebView;
import android.widget.ViewFlipper;
public class CustomWebView extends WebView {
private float lastX;
private ViewFlipper viewFlipper;
float downXValue;
long downTime;
private float lastTouchX, lastTouchY;
private boolean hasMoved = false;
public CustomWebView(Context context) {
super(context);
viewFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
}
public boolean onTouchEvent(MotionEvent evt) {
boolean consumed = super.onTouchEvent(evt);
if (isClickable()) {
switch (evt.getAction()) {
case MotionEvent.ACTION_DOWN:
lastTouchX = evt.getX();
lastTouchY = evt.getY();
downXValue = evt.getX();
downTime = evt.getEventTime();
hasMoved = false;
break;
case MotionEvent.ACTION_MOVE:
hasMoved = moved(evt);
break;
case MotionEvent.ACTION_UP:
float currentX = evt.getX();
long currentTime = evt.getEventTime();
float difference = Math.abs(downXValue - currentX);
long time = currentTime - downTime;
if ( (downXValue < currentX) && (time < 220) && (difference > 100) ) {
if (viewFlipper.getDisplayedChild() == 1)
break;
viewFlipper.setInAnimation(getContext(), R.anim.slide_in_from_right); // Next screen comes in from right.
viewFlipper.setOutAnimation(getContext(), R.anim.slide_out_to_left); // Current screen goes out from left.
// Display previous screen.
viewFlipper.showPrevious();
}
if ( (downXValue > currentX) && (time < 220) && (difference > 100) ) {
if (viewFlipper.getDisplayedChild() == 0)
break;
viewFlipper.setInAnimation(getContext(), R.anim.slide_in_from_left); // Next screen comes in from left.
viewFlipper.setOutAnimation(getContext(), R.anim.slide_out_to_right); // Current screen goes out from right.
// Display next screen.
viewFlipper.showNext();
}
//if (!moved(evt)) performClick();
break;
}
}
return consumed || isClickable();
}
private boolean moved(MotionEvent evt) {
return hasMoved || Math.abs(evt.getX() - lastTouchX) > 10.0 || Math.abs(evt.getY() - lastTouchY) > 10.0;
}
/*public boolean onTouchEvent(MotionEvent touchevent) {
switch (touchevent.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = touchevent.getX();
break;
case MotionEvent.ACTION_UP:
float currentX = touchevent.getX();
// Handling left to right screen swap.
if (lastX < currentX) {
// If there aren't any other children, just break.
if (viewFlipper.getDisplayedChild() == 0)
break;
viewFlipper.setInAnimation(getContext(), R.anim.slide_in_from_left); // Next screen comes in from left.
viewFlipper.setOutAnimation(getContext(), R.anim.slide_out_to_right); // Current screen goes out from right.
// Display next screen.
viewFlipper.showNext();
}
// Handling right to left screen swap.
if (lastX > currentX) {
// If there is a child (to the left), just break.
if (viewFlipper.getDisplayedChild() == 1)
break;
viewFlipper.setInAnimation(getContext(), R.anim.slide_in_from_right); // Next screen comes in from right.
viewFlipper.setOutAnimation(getContext(), R.anim.slide_out_to_left); // Current screen goes out from left.
// Display previous screen.
viewFlipper.showPrevious();
}
break;
}
return false;
}*/
}
For some reason, viewFlipper is unknown at this point. HOWEVER if I replace the onTouchEvent with the previous onTouchEvent which is located at the bottom, it all works fine and I can't understand why! Any ideas?
EDIT: Layout xml file
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingTop="10dp" >
<ViewFlipper
android:id="#+id/viewFlipper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.70" />
<LinearLayout
android:id="#+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/actionbar_bg" >
<Button
android:id="#+id/btn_left"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_weight="0.23"
android:text="#string/previous" />
<TextView
android:id="#+id/page_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.23"
android:gravity="center"
android:text=""
android:textColor="#FFFFFF" />
<Button
android:id="#+id/btn_right"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_weight="0.35"
android:text="#string/next" />
</LinearLayout>
</LinearLayout>
The problem is that CustomWebView can't find R.id.viewFlipper with findViewById.
What you need to do is lookup R.id.viewFlipper in the ParentActivity and pass the ViewFlipper in the CustomWebView constructor.
Example code below, not compiled/tested.
CustomWebView.java
public CustomWebView(Context context, ViewFlipper viewFlipper) {
super(context);
this.viewFlipper = viewFlipper;
}
ParentActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout_xml);
ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.viewFlipper);
CustomWebView webView = new CustomWebView(this, viewFlipper);
// TODO: Add webView to viewFlipper
}
I'm developing an Android app using Xamarin studio, but this is not important.
I use the pageViewer to upload the fragment (12 more or less). To the one display fragment i use a webView to display a local html page and with a swipe to the left, the webview content change to the next one.
So, at the fifth frgament memory problems started even if i used different tasks in my code.
My question is: Is there a way to 'detach' the fragment when i'm not displaying them? Can they not remain into my memory?
Thanks for all
This is my code, N.B: Java answers are accepeted as well
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView (Resource.Layout.BookView);
_loader = ProgressDialog.Show (this, "Loading...", "Please wait...", true);
//return chapters count
chapters = 12 //example
//var dp = (int)Resources.DisplayMetrics.Density;
_layout = FindViewById<DrawerLayout> (Resource.Id.drawer_layout);
_view = FindViewById<ViewPager>(Resource.Id.bView);
_view.SetBackgroundColor (Color.White);
_currentAdapter = new AwesomeFragmentAdapter (SupportFragmentManager, path, name, chapters, this, _view);
_view.Adapter = _currentAdapter;
_view.OffscreenPageLimit = chapters;
List<int> positions = new List<int> ();
_view.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) => {
//get details
var page_load = new Task (() => {
//return an object with the chapter details
_chap = object;
});
page_load.Start();
//find the webview
_web = (WebView)_view.FindViewWithTag(300 + e.Position);
WebSettings setting = _web.Settings;
setting.CacheMode = CacheModes.Default;
setting.JavaScriptEnabled = true;
setting.BuiltInZoomControls = true;
setting.DisplayZoomControls = false;
setting.PluginsEnabled = true;
setting.SetPluginState(WebSettings.PluginState.On);
//setting.JavaScriptCanOpenWindowsAutomatically = true;
if (positions.Contains(e.Position)) {
_web.ClearCache(true);
_web.ClearView();
}
//Start when the scroll is finished
_view.PageScrollStateChanged += (object sendero, ViewPager.PageScrollStateChangedEventArgs ex) => {
if (ex.State == 0 ) {
if (positions.Contains(e.Position)) {
//_web.Reload(); --> doesn't work
//Doesn't reload the .js animations
_web.LoadUrl ("file://" + path + "/" + _chap.Name);
} else {
_web.LoadUrl ("file://" + path + "/" + _chap.Name);
positions.Add(e.Position);
}
}
};
};
}
public class BWebClient : WebViewClient
{
int _position;
string _path;
Activity _parent;
ViewPager _pager;
string _chapName;
public BWebClient (int position, string Path, Activity Parent, ViewPager Pager, string ChapName){
_position = position;
_parent = Parent;
_path = Path;
_pager = Pager;
_chapName = ChapName;
}
public override void OnPageFinished (WebView view, string url)
{
base.OnPageFinished (view, url);
view.ScrollTo (0, _position);
}
public override bool ShouldOverrideUrlLoading (WebView view, string url)
{
if (url.StartsWith ("navigate")) {
string destination = url.Substring (url.IndexOf ("navigate://") + "navigate://".Length);
int DestinationChapter = Int32.Parse (destination.Substring (0, destination.IndexOf("_")));
int l = destination.IndexOf("_") + 1;
int b = destination.Length - l;
int DestinationPage = Int32.Parse (destination.Substring (l,b));
if (DestinationPage == 0) {
_pager.SetCurrentItem(DestinationChapter ,true);
WebView _web = (WebView)_pager.FindViewWithTag(300 + DestinationChapter);
_web.LoadUrl ("file://" + _path + "/" + _chapName);
}
} else if (url.StartsWith ("pdf")) {
string file_path = System.IO.Path.Combine (_path, url.Substring (url.IndexOf ("pdf://") + "pdf://".Length));
Android.Net.Uri pdfFile = Android.Net.Uri.FromFile (new Java.IO.File (file_path));
Intent pdfIntent = new Intent (Intent.ActionView);
pdfIntent.SetDataAndType (pdfFile, "application/pdf");
_parent.StartActivity (pdfIntent);
}
return true;
}
}
public class AwesomeFragmentAdapter : FragmentPagerAdapter
{
string _path;
string _filename;
int _chapters;
Activity _parent;
FileUtilities _fUtils;
ViewPager _pager;
public AwesomeFragmentAdapter (Android.Support.V4.App.FragmentManager fm,
string path,
string filename,
int chapters,
Activity parent,
FileUtilities FUtils,
ViewPager Pagers): base(fm)
{
_path = path;
_filename = filename;
_chapters = chapters;
_parent = parent;
_fUtils = FUtils;
_pager = Pagers;
}
public override int Count
{
/* --- return chapter count --- */
get { return _chapters;}
}
public override Android.Support.V4.App.Fragment GetItem(int _position)
{
/* --- get specific item --- */
return new AwesomeFragment (_path, _filename, _position, _parent, _fUtils, _pager);
}
}
public class AwesomeFragment : Android.Support.V4.App.Fragment
{
string _path;
WebView web_view;
string _filename;
int _position;
Activity _parent;
BanjiChapter _chap;
FileUtilities _fUtils;
ViewPager _pager;
public AwesomeFragment () {}
public AwesomeFragment (string path,
string filename,
int position,
Activity parent,
FileUtilities FUtils,
ViewPager Pager)
{
_path = path;
_filename = filename;
_position = position;
_parent = parent;
_fUtils = FUtils;
_pager = Pager;
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
/* --- Create the view --- */
var view = inflater.Inflate (Resource.Layout.BookWebView, container, false);
//return the chapter
_chap = _fUtils.ReturnChapterDetails(_filename, _position);
web_view = view.FindViewById<WebView> (Resource.Id.webview);
web_view.SetWebViewClient(new BanjiWebClient(_position,_path,_parent, _pager, _chap.Name ));
web_view.SetBackgroundColor(Color.Transparent);
web_view.Settings.JavaScriptEnabled = true;
web_view.Tag = 300 + _position;
switch(Resources.DisplayMetrics.DensityDpi){
case Android.Util.DisplayMetricsDensity.Medium:
{
web_view.SetLayerType (LayerType.Software, null);
break;
}
case Android.Util.DisplayMetricsDensity.High:
{
web_view.SetLayerType (LayerType.Hardware, null);
break;
}
case Android.Util.DisplayMetricsDensity.Xhigh:
{
web_view.SetLayerType (LayerType.Hardware, null);
break;
}
}
if (_chap.Background == null) {
view.SetBackgroundColor (Color.White);
} else {
view.SetBackgroundDrawable (new BitmapDrawable (BitmapFactory.DecodeByteArray (_chap.Background, 0, _chap.Background.Length)));
}
if (_position == 0) {
web_view.LoadUrl ("file://" + _path + "/" + _chap.Name);
}
return view;
}
public BChapter GetCurrentBChapter()
{
return _chap;
}
}
EDIT:
BookView.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffececec">
<!-- The main content view -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainView">
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/bookView" />
<ImageButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="left|center"
android:background="#null"
android:id="#+id/menuButton"
/>
</FrameLayout>
<!-- The navigation drawer -->
<LinearLayout
android:id="#+id/left_menu"
android:layout_width="250dp"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:layout_gravity="start"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#111">
<Button
android:id="#+id/backStep"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/button_text"
android:background="#ff000000"
android:fitsSystemWindows="false" />
<Space
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/screllArea">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/ThumbLayout" />
</ScrollView>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
BookWebView
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Use a FragmentStatePagerAdapter instead; it is designed to minimise memory overhead by possibly destroying the fragment when it is not visible, saving only the state information of that fragment.
From the developers docs:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
See here for documentation.
I am developing an android app, using eclipse IDE, part of which reads data through a remote gateway, and the information is then presented as a table - using TableLayout and TableRow, dynamically in Java. I want to be able to show the data and then select an element, and based on this, carry out some other actions.
The extract of data, and the generation of the screen and the list works fine. I have set it to be clickable, and set an appropriate onClickListener. I am able to get into the onClick method (Proven by having output with v.getId()) - (seen on the LogCat of the Eclipse IDE), within the onClickListener. However when trying to v.getParent within the onClick method, the Eclipse emulator fails. The error message shown after the VM shuts down shows :
java.lang.ClassCastException: android.widget.TableLayout cannot be cast to android.widget.TableRow.
I have spent a couple of days trying to identify problem, but obviously don't understand what I have wrong, so am reaching out for help. Whether this is a nudge in the right direction, or specifically telling me what is wrong, any help would be greatly appreciated.
Ultimately I would like to extract one of the fields in the TableRow (node) - which is a unique integer address, and use this for further processing.
Java Code is :
public void readNodeKeys(int[] value) {
setContentView(R.layout.testrz_nodes);
TabHost node_host = (TabHost) findViewById(R.id.tabhost);
node_host.setup();
TabSpec nodeTab = node_host.newTabSpec("node_tabs");
nodeTab.setIndicator(getResources().getString(R.string.node_information),
getResources().getDrawable(android.R.drawable.star_on));
nodeTab.setContent(R.id.node_ScrollView2);
node_host.addTab(nodeTab);
node_host.setCurrentTabByTag("node_tabs");
TableLayout list_table = (TableLayout) findViewById(R.id.node_TableLayout2);
initializeHeaderRow(list_table);
try {
processScores(list_table, value);
} catch (Exception e) {
Log.e(DEBUG_TAG, "Failed to load list status data", e);
}
}
public void initializeHeaderRow(TableLayout statusTable) {
TableRow headerRow = new TableRow(this);
int textColor = getResources().getColor(R.color.testrz_logo_color);
float textSize = getResources().getDimension(R.dimen.testrz_nodelist_text_size_6);
addTextToRowWithValues(headerRow, getResources().getString(R.string.testrz_nodelist_node), textColor, textSize);
addTextToRowWithValues(headerRow, getResources().getString(R.string.testrz_nodelist_data_index), textColor, textSize);
addTextToRowWithValues(headerRow, getResources().getString(R.string.testrz_nodelist_zone_A), textColor, textSize);
addTextToRowWithValues(headerRow, getResources().getString(R.string.testrz_nodelist_zone_B), textColor, textSize);
addTextToRowWithValues(headerRow, getResources().getString(R.string.testrz_nodelist_zone_C), textColor, textSize);
addTextToRowWithValues(headerRow, getResources().getString(R.string.testrz_nodelist_module_type), textColor, textSize);
statusTable.addView(headerRow);
}
public void addTextToRowWithValues(final TableRow tableRow, String text, int textColor, float textSize) {
TextView textView = new TextView(this);
textView.setTextSize(textSize);
textView.setTextColor(textColor);
textView.setText(text);
tableRow.setClickable(true);
tableRow.setOnClickListener(tableRowOnClickListener);
tableRow.addView(textView);
}
public OnClickListener tableRowOnClickListener = new OnClickListener () {
public void onClick(View v) {
System.out.println("Row Clicked : " + v.getId());
TableRow tr = (TableRow)v.getParent();
System.out.println("We've got to the TableRow tr");
TextView tv = (TextView) tr.getChildAt(index);
System.out.println("We've got to the TableView tv");
String result = tv.getText().toString();
System.out.println("On Click string is : " + result);
}
};
public void processScores(final TableLayout statusTable, int[] value) throws
IOException {
int counter = 0;
System.out.println("mtestrz_Node_Count : " + Integer.toHexString(mtestrz_Node_Count));
for (counter = 0; counter < mtestrz_Node_Count; counter++) {
testrz_Node_Key_Values RNKV = new testrz_Node_Key_Values();
offset = 0x10;
while (value[(counter * offset)+18] != 0xff) {
int eventType = -1;
boolean bFoundScores = false;
// Find list
nodelist[mtestrz_Node_Count][0] = value[(counter * offset)+18];
nodelist[mtestrz_Node_Count][1] = value[(counter * offset)+19];
nodelist[mtestrz_Node_Count][2] = value[(counter * offset)+20];
nodelist[mtestrz_Node_Count][3] = value[(counter * offset)+21];
nodelist[mtestrz_Node_Count][4] = value[(counter * offset)+22];
nodelist[mtestrz_Node_Count][5] = value[(counter * offset)+23];
nodelist[mtestrz_Node_Count][6] = value[(counter * offset)+24];
nodelist[mtestrz_Node_Count][7] = value[(counter * offset)+25];
nodelist[mtestrz_Node_Count][8] = value[(counter * offset)+26];
nodelist[mtestrz_Node_Count][9] = value[(counter * offset)+27];
nodelist[mtestrz_Node_Count][10] = value[(counter * offset)+28];
nodelist[mtestrz_Node_Count][11] = value[(counter * offset)+29];
nodelist[mtestrz_Node_Count][12] = value[(counter * offset)+30];
nodelist[mtestrz_Node_Count][13] = value[(counter * offset)+31];
nodelist[mtestrz_Node_Count][14] = value[(counter * offset)+32];
nodelist[mtestrz_Node_Count][15] = value[(counter * offset)+33];
RNKV.Node_add = nodelist[mtestrz_Node_Count][1] * 0x100 + nodelist[mtestrz_Node_Count][0];
RNKV.Code_Control = nodelist[mtestrz_Node_Count][3] * 0x100 + nodelist[mtestrz_Node_Count][2];
RNKV.Data_index = nodelist[mtestrz_Node_Count][5] * 0x100 + nodelist[mtestrz_Node_Count][4];
RNKV.Zone_A = nodelist[mtestrz_Node_Count][7] * 0x100 + nodelist[mtestrz_Node_Count][6];
RNKV.Zone_B = nodelist[mtestrz_Node_Count][9] * 0x100 + nodelist[mtestrz_Node_Count][8];
RNKV.Zone_C = nodelist[mtestrz_Node_Count][11] * 0x100 + nodelist[mtestrz_Node_Count][10];
RNKV.Module_type = nodelist[mtestrz_Node_Count][12];
Module_Type module_str;
module_str = (module_type_str[RNKV.Module_type]);
String module_string;
module_string = module_str.toString();
bFoundScores = true;
String node = Integer.toHexString(RNKV.Node_add);
String data_index = Integer.toHexString(RNKV.Data_index);
String Zone_A = Integer.toHexString(RNKV.Zone_A);
String Zone_B = Integer.toHexString(RNKV.Zone_B);
String Zone_C = Integer.toHexString(RNKV.Zone_C);
String module_type = module_string;
insertStatusRow(statusTable, node, data_index, Zone_A, Zone_B, Zone_C, module_type);
// Handle no scores available
if (bFoundScores == false) {
final TableRow newRow = new TableRow(this);
TextView noResults = new TextView(this);
noResults.setText(getResources().getString(R.string.testrz_no_data));
newRow.addView(noResults);
statusTable.addView(newRow);
}
counter = counter + 1;
}
}
}
public void insertStatusRow(final TableLayout statusTable, String node, String data_index, String Zone_A, String Zone_B, String Zone_C, String module_type) {
final TableRow newRow = new TableRow(this);
int textColor = getResources().getColor(R.color.testrz_title_color);
float textSize = getResources().getDimension(R.dimen.testrz_nodelist_text_size_6);
addTextToRowWithValues(newRow, node, textColor, textSize);
addTextToRowWithValues(newRow, data_index, textColor, textSize);
addTextToRowWithValues(newRow, Zone_A, textColor, textSize);
addTextToRowWithValues(newRow, Zone_B, textColor, textSize);
addTextToRowWithValues(newRow, Zone_C, textColor, textSize);
addTextToRowWithValues(newRow, module_type, textColor, textSize);
statusTable.addView(newRow);
}
XML Code is :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="#+id/rayzig_nodes_RelativeLayout02"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/rayzig_nodes_Textview02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="#string/nodes" />
<DigitalClock
android:id="#+id/rayzig_nodes_digitalClock2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_below="#+id/rayzig_nodes_Textview02"
android:layout_marginTop="17dp"
android:text="#string/clock" />
</RelativeLayout>
<TabHost
android:id="#+id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TabWidget
android:id="#android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</TabWidget>
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:id="#+id/node_ScrollView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TableLayout
android:id="#+id/node_TableLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stretchColumns="*" >
</TableLayout>
</ScrollView>
</FrameLayout>
</LinearLayout>
</TabHost>
</LinearLayout>
Onclick listner is an anonymous inner class,
So try this inside your onclick:
View parent = (View)v.getParent();
if (parent != null) {
TableRow tr = parent.findViewById(R.id.parent);//your xml resource
}
tablerow parant is tableLayout so change this like
TableLayout tablelayout= (TableLayout )v.getParent();