Android - Predraw canvas for onDraw - android-canvas

I used to do my drawing in an ImageView in the onDraw method.
However, I've learnt that's better to draw the canvas outside of the onDraw and just update the canvas in onDraw.
I know this is clearly wrong (because it's not working) but how would I accomplish what I'm trying to do:
#Override
public void onDraw(Canvas c) {
c = this.newCanvas;
super.onDraw(c);
}

public class GameLoopThread extends Thread {
private GameView view;
private boolean running = false;
public GameLoopThread(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while (running) {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
if (c != null) {
view.onDraw(c);
}
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
try {
sleep(10);
} catch (Exception e) {}
}
}
}
make that thread then in your activity do something like this
#Override
public void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(new GameView(GameActivity.this));
}
then in a GameViewClass do something like this
public class GameView extends SurfaceView {
private SurfaceHolder holder;
private GameLoopThread gameLoopThread;
public GameView(Context context) {
super(context);
gameLoopThread = new GameLoopThread(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
}
#Override
protected void onDraw(Canvas canvas) {
//Do Drawing
}
}
The important things here is that the thread is manually auto calling the onDraw() method repeatedly, and that you are locking a canvas, drawing on it, and then posting it. If you dont need a super fast refresh rate then you might be better off doing something like this:
#Override
public void onDraw(Canvas c) {
c = this.getHolder().lockCanvas();
if (c != null) {
//draw on canvas
}
if (c != null) {
this.getHolder().unlockCanvasAndPost(c);
}
}
I just dont know if that last bit there will work, never tested it.
also if you want to do your drawing outside the on draw method, you could run your updating (drawing on your canvas) in a thread, and every time the onDraw method is called have it check to see if the Canvas is ready for it to post. for example have your thread have a boolean that once the canvas gets pulled it is set to false, so the thread will draw you a new one, but once it is done drawing set the boolean to true. in the ondraw method check to see if the boolean is true and if it is pull the canvas.

A Canvas is just a handle for drawing onto something -- you need to get at the something itself. The Canvas that you draw into outside of onDraw() needs to be backed by a Bitmap. Then in onDraw(), simply draw that Bitmap into the Canvas provided:
Bitmap my_bitmap = null; /* this needs to be initialized whereever it is drawn into */
#Override
public void onDraw(Canvas c) {
if (my_bitmap != null) {
c.drawBitmap(my_bitmap, 0.0, 0.0, null);
}
}
onSizeChanged() would be a reasonable place to initialize the Bitmap, because then you know its size:
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
my_bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
And to draw on my_bitmap, just make a new Canvas with:
Canvas c = new Canvas(my_bitmap);

Related

How to track randomly displayed drawable to cross check with user submitted answer

I am trying to create an application where a random image is displayed(Working), and the user selects from a dropdown list the displayed image and this is checked to see if it is correct or not
I am not sure how to track the image displayed as I need something to reference in my if statement
See below my attempt but it returns null on the locationOfCorrectAnswer
public class IdentifyTheBrandActivity extends AppCompatActivity implements AdapterView.OnItemClickListener,
AdapterView.OnItemSelectedListener { String[] Brands = { "Audi", "Bentley", "BMW" };
Button SubmitButton;
ImageView imageView;
int[] images;
int chosenCar = 0;
int locationOfCorrectAnswer;
String[] answers = new String[3];
ArrayList<String> carBrands = new ArrayList<>();
public void brandChosen(View view) {
if (view.getTag().toString().equals(Integer.toString(locationOfCorrectAnswer))){
Toast.makeText(getApplicationContext(), "Correct", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(getApplicationContext(), "Wrong it was " + carBrands.get(chosenCar), Toast.LENGTH_LONG).show();
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_identify_the_brand);
imageView = findViewById(R.id.RandomImage);
imageView.setTag("bg");
carBrands.add("Audi");
carBrands.add("Bentley");
carBrands.add("BMW");
images = new int[]{R.drawable.audi_a3_2010_27_17_200_20_4_77_56_169_21_fwd_5_4_4dr_igm, R.drawable.bentley_bentayga_2017_229_21_600_60_12_78_68_202_12_awd_5_4_suv_cce,
R.drawable.bmw_6_series_2014_82_18_310_30_6_74_53_192_20_rwd_4_2_convertible_mua};
Random random = new Random();
chosenCar = random.nextInt(images.length);
locationOfCorrectAnswer = random.nextInt(images.length);
imageView.setBackgroundResource(images[chosenCar]);
int incorrectAnswerLocation;
//Get a random between 0 and images.length-1
//int imageId = (int) (Math.random() * images.length);
Spinner spin = (Spinner) findViewById(R.id.spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, Brands);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spin.setAdapter(adapter);
spin.setOnItemSelectedListener(this);
}
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long l) {
Toast.makeText(getApplicationContext(), "Selected Brand: " + Brands[position]
,Toast.LENGTH_SHORT).show();
/*SubmitButton = findViewById(R.id.SubmitButton);
SubmitButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Toast.makeText(getApplicationContext(), "Correct Brand", Toast.LENGTH_SHORT).show();
}
});*/
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
}
I'm not sure why my brandChosen method is returning null? I believe this is the approach that will work

Restore scroll position

I have a simple fragment which retrieve data from Firebase database.
I use firebase recycler view to display retrieving data. And after scrolling or screen rotation I can't force recycler view (or linear layout manager) restore scroll position.
I found here some answers but they don't work.
My code is:
public class NewsListFragment extends ParentNewsFragment {
static int color_naval, color_black;
private boolean mProcessLikes = false;
private DatabaseReference mDatabaseLikes;
private DatabaseReference mDatabaseViews;
private FirebaseAuth mAuth;
private int position = 0;
public static NewsListFragment getInstance() {
return new NewsListFragment();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDatabaseReference = FirebaseDatabase.getInstance().getReference()
.child("App_news");
mDatabaseLikes = FirebaseDatabase.getInstance().getReference().child("news_likes");
mDatabaseViews = FirebaseDatabase.getInstance().getReference().child("news_views");
mAuth = FirebaseAuth.getInstance();
mQuery = mDatabaseReference.orderByChild("pos").startAt("100");
color_naval = getResources().getColor(R.color.colorPrimary);
color_black = getResources().getColor(R.color.colorBlack);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.rv_choose, container, false);
rv = (RecyclerView) rootView.findViewById(R.id.rv_choose);
lm = new LinearLayoutManager(getActivity());
rv.setLayoutManager(lm);
rv.setHasFixedSize(true);
return rootView;
}
#Override
public void onPause() {
super.onPause();
position = lm.findFirstVisibleItemPosition();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", position);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(savedInstanceState != null) {
position = savedInstanceState.getInt("position");
}
}
#Override
public void onResume() {
super.onResume();
if (position != 0) {
lm.scrollToPosition(position);
showToast(position+"");
}
}
#Override
public void onStart() {
super.onStart();
FirebaseRecyclerAdapter<NewsList, NewsListViewHolder> adapter =
new FirebaseRecyclerAdapter<NewsList, NewsListViewHolder>(
NewsList.class,
R.layout.frag_newslist_card_view,
NewsListViewHolder.class,
mQuery
) {
#Override
protected void populateViewHolder(NewsListViewHolder viewHolder, final NewsList model, int position) {
final String post_key = getRef(position).getKey();
viewHolder.setDate(model.getDate()+",");
viewHolder.setTime(model.getTime());
viewHolder.setTitle(model.getTitle());
viewHolder.setImage(getContext(), model.getImage());
viewHolder.setEye(model.getCode());
viewHolder.setThumb(post_key);
viewHolder.thumb.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mProcessLikes = true;
mDatabaseLikes.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (mProcessLikes){
if (dataSnapshot.child(post_key).hasChild(mAuth.getCurrentUser().getUid())){
mDatabaseLikes.child(post_key).child(mAuth.getCurrentUser().getUid())
.removeValue();
mProcessLikes = false;
}
else {
mDatabaseLikes.child(post_key).child(mAuth.getCurrentUser().getUid())
.setValue("like");
mProcessLikes = false;
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
});
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mDatabaseViews.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (!dataSnapshot.child(post_key).hasChild(mAuth.getCurrentUser().getUid())) {
mDatabaseViews.child(post_key).child(mAuth.getCurrentUser().getUid())
.setValue("view");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
mChooserListener.chooseNews(model.getCode());
}
});
}
};
rv.setAdapter(adapter);
}
public static class NewsListViewHolder extends RecyclerView.ViewHolder {
//some text here
}
}
method showToast in the end shows real number of position but recyclerview starts from the beginning.
any ideas?
Make sure you are not reloading data from the server or wherever you are retrieving data.
Add is not null check in your fragment's onCreateView or activity's onCreate like:
if(savedInstanceState != null){
...
}else{
loadData();
}
Replace your linear layout manager with something similar to the following. I have used this implementation many times when using RecyclerView in fragments. Let me know how it goes
LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
yourItem.setLayoutManager(mLayoutManager);

RecycleView , some items' subView doesn't show correctly

in fragment
mAdapter = new MessageAdapter(this);
mRV.setLayoutManager(new LinearLayoutManager(getActivity(),LinearLayoutManager.VERTICAL,false));
mRV.setItemAnimator(new DefaultItemAnimator());
DividerItemDecoration itemDecoration = new DividerItemDecoration.Builder()
.setOffsetLeft(ScreenUtil.dip2px(getActivity(), 60 + 10) + this.getResources().getDimensionPixelOffset(R.dimen.horizontal_margin))
.build(getActivity());
mRV.addItemDecoration(itemDecoration);
mRV.setItemViewCacheSize(15);
mRV.setAdapter(mAdapter);
in adapter
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MessageItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.w_message_item,parent,false));
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((RVItemInterface)holder).setAdapter(this);
((RVItemInterface)holder).update(dataList.get(position),position);
}
in holder
public class MessageItemHolder extends RecyclerView.ViewHolder implements RVItemInterface{
private RoundAvatar mAvatar;
private TextView mTitle;
private TextView mContent;
private TextView mTime;
private BaseRVAdapter mAdapter;
private MsgDecorVo mMsgDecorVo;
...
#Override
public void update(Object obj, final int position) {
reset();
mMsgDecorVo = (MsgDecorVo) obj;
if(mMsgDecorVo.type == MsgDecorVo.TYPE_CATEGORY){
updateCategory();
MsgVo msgVo = mMsgDecorVo.msgVo;
if(msgVo.getMsg() == null || msgVo.getMsg().equals("")){
mContent.setVisibility(View.GONE);
}else {
mContent.setVisibility(View.VISIBLE);
mContent.setText(msgVo.getMsg());
}
if(msgVo.getTime() == 0){
mTime.setVisibility(View.GONE);
}else {
mTime.setVisibility(View.VISIBLE);
mTime.setText(TimeUtil.transformLong2DateString(msgVo.getTime()));
}
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dealCategoryClick();
}
});
}else if(mMsgDecorVo.type == MsgDecorVo.TYPE_CONVERSATION){
...
mContent.setText(ImUtil.getMsgContent(message));
mTime.setText(TimeUtil.transformLong2DateString(message.getMsgTime()));
if(chatUserVo != null){
..
}else {
..
}
}
}
//
private void updateCategory(){
...
}
//
private void dealCategoryClick(){
...
}
#Override
public void setAdapter(RecyclerView.Adapter adapter) {
mAdapter = (BaseRVAdapter) adapter;
}
#Override
public void reset() {
mAvatar.setOnClickListener(null);
mAvatar.setAvatar(R.drawable.avatar_default_circle);
mAvatar.hideTagBottom();
mAvatar.hideTagTop();
}
}
these code in holder
mContent.setText(ImUtil.getMsgContent(message));
mTime.setText(TimeUtil.transformLong2DateString(message.getMsgTime()));
has run
but when I scrolled,some of items doesn't show correctly ,the time and content was gone!
if I notify the recyclerview ,it goes right, and if i scroll again,it will still be wrong
just like the image,you can see some items' (time & content) was gone!
http://g.picphotos.baidu.com/album/s%3D900%3Bq%3D90/sign=297cc7510946f21fcd345253c61f1a5d/a686c9177f3e6709378bcc5538c79f3df9dc5595.jpg "tooltip"
Well, you don't have the full code but I assume it is happening because you are not resetting your view states properly.
For Instance, in your updateCode, if type = TYPE_CATEGORY, you set the mTime's visibility depending on whether getTime is 0 or not. But as you scroll, that row might be re-used for
TYPE_CONVERSATION in which case, mTime's visibility will NOT be updated.

GWT popup is not centered when built within onClickHandler

My aim is to use GWT.runSync to load the popup contents only when required.
If I construct my widget as:
public class CreateButton extends Button {
public CreateButton() {
super("Create");
buildUI();
}
private void buildUI() {
final CreateWidget createWidget = new CreateWidget();
final PopupPanel popupPanel = new PopupPanel(false);
popupPanel.setWidget(createWidget);
popupPanel.setGlassEnabled(true);
popupPanel.setAnimationEnabled(true);
addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
popupPanel.center();
}
});
}
}
Then the popup will be centered correctly.
If I build the popup within the clickHandler:
public class CreateButton extends Button {
public CreateButton() {
super("Create");
buildUI();
}
private void buildUI() {
#Override
public void onClick(ClickEvent event) {
final CreateWidget createWidget = new CreateWidget();
final PopupPanel popupPanel = new PopupPanel(false);
popupPanel.setWidget(createWidget);
popupPanel.setGlassEnabled(true);
popupPanel.setAnimationEnabled(true);
addClickHandler(new ClickHandler() {
popupPanel.center();
}
});
}
}
The popup will not center correctly. I have tried using setPositionAndShow, however the supplied offsets are 12, even though the CreateWidget is actually about 200px for both width and height.
I want to use the second method so I can eventually use GWT.runAsync within the onClick as CreateWidget is very complex.
I am using GWT-2.1.1
Seems to work by delaying the call to center. Perhaps a once off Timer would work as well. Delaying the call also works when wrapping buildUI within GWT.runAsync
public class CreateButton extends Button {
public CreateButton() {
super("Create");
buildUI();
}
private void buildUI() {
#Override
public void onClick(ClickEvent event) {
final CreateWidget createWidget = new CreateWidget();
final PopupPanel popupPanel = new PopupPanel(false);
popupPanel.setWidget(createWidget);
popupPanel.setGlassEnabled(true);
popupPanel.setAnimationEnabled(true);
addClickHandler(new ClickHandler() {
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
#Override
public boolean execute() {
popupPanel.center();
return false;
}
}, 50); //a value greater than 50 maybe needed here.
});
}
}
}
}

SurfaceView Tutorial problems

I found a tutorial and it looks like this:
package com.djrobotfreak.SVTest;
public class Tutorial2D extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new Panel(this));
}
class Panel extends SurfaceView implements SurfaceHolder.Callback {
private TutorialThread _thread;
public Panel(Context context) {
super(context);
getHolder().addCallback(this);
_thread = new TutorialThread(getHolder(), this);
}
#Override
public void onDraw(Canvas canvas) {
Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(_scratch, 10, 10, null);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
_thread.setRunning(true);
_thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// simply copied from sample application LunarLander:
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
}
class TutorialThread extends Thread {
private SurfaceHolder _surfaceHolder;
private Panel _panel;
private boolean _run = false;
public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) {
_run = run;
}
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
_panel.onDraw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
and it does not work, no matter what I do. I am trying to convert my code to surfaceview but I cant find any surfaceview programs that even work (besides the android-provided ones). Does anyone know what the error even is saying?
Here is my logcat info: http://shrib.com/oJB5Bxqs
If you get a ClassNotFoundException, you should check the Manifest file.
Click on the Application tab and look on the botton right side under "Attributes for".
If there is a red X mark under your Class Name, then click on the "Name" link and locate the correct class to load.